#!/usr/bin/env bash
shopt -s checkwinsize || return 1; (:;:)
با استفاده از یک No Op ترمینال رو گول میزنیم تا توهم تعامل بزنه (😂) و متغیرها رو ست کنه. میتونستیم از
/usr/bin/true
هم استفاده کنیم ولی خیلی کار جالبی نیست!
عبارت (:;:)
در اصل هیچکاری انجام نمیدهد اما باعث میشود تا ترمینال دو متغیر مورد نیاز را به شل گزارش دهد.
درباره noop بیشتر بخوانید
در قسمت بعدی مختصات میانهی ترمینال رو محاسبه میکنیم. این کار ضروری نیست. میتوانستیم به سادگی x و y رو به COLUMNS و LINES اختصاص بدیم یا حتا مستقیما از COLUMNS و LINES استفاده کنیم. دقت کنید که در نحو ریاضی بش، یعنی در Arithmetic Syntax ،نیازی به استفاده از سیجیل $ برای متغیرها نداریم.
x=$(( COLUMNS / 2 ))
y=$(( LINES / 2 ))
printf "\e[10;20H Hello"
printf "\e[%d;%dH Hello" 10 20
پس تا اینجای کار کد ما به شکل زیر است:
#!/usr/bin/env bash
shopt -s checkwinsize || return 1; (:;:)
x=$(( COLUMNS / 2 ))
y=$(( LINES / 2 ))
printf "\e[%d;%dH Hello" $y $x
به ترتیب از چپ، بالا پایین چپ راست.
برای خواندن یک متغیر از STDIN طبق معمول از read استفاده میکنیم.
بنابراین برای دنبال کردن کلیدهایی که توسط کاربر فشرده میشوند به شیوهی زیر عمل میکنیم:
read -rsn3 key
فلگهای rsn3 به ترتیب برای خواندن بکاسلشها، سکوت خروجی و خواندن مشخصا ۳ حرف از ورودی استفاده میشوند.
برای اطلاعات بیشتر، man read
را ببینید.
حالا با ترکیب یک لوپ و یک کیس مقادیر x و y را تغییر داده، ترمینال را پاک کرده و عبارت را مجدد چاپ میکنیم تا حرکت عبارت میسر شود.
while :; do
read -rsn3 key
clear
case "$key" in
$'\e[A') ((y--));;
$'\e[B') ((y++));;
$'\e[C') ((x++));;
$'\e[D') ((x--));;
esac
printf "\e[%d;%dH Hello" $y $x
done
نحو خاص ;:
به معنی true است.
تمام کد تا اینجا:
#!/usr/bin/env bash
shopt -s checkwinsize || return 1; (:;:)
x=$(( COLUMNS / 2 ))
y=$(( LINES / 2 ))
while :; do
printf "\e[%d;%dH Hello" $y $x
read -rsn3 key
clear
case "$key" in
$'\e[A') ((y--));;
$'\e[B') ((y++));;
$'\e[C') ((x++));;
$'\e[D') ((x--));;
esac
done
دقت کنید که در صورتی که printf بعد از case قرار بگیرد، تا دریافت کلیدی از کاربر چیزی چاپ نخواهد شد.
- نشانگر پنهان نشده است
- ترمینال به موقع پاک نمیشود
- محتوای پیشین ترمینال حفظ نمیشود
printf "\e[?25l"
دستور انسی بالا به شکل echo -ne "\033[?25l"
یا printf "\033[?25l"
نیز میتوانست نوشته شود. ( این قاعده بر باقی دستورات هم صدق میکند)
هردوی e\ و 033\ کد اسکی ۲۷ (ASCII 27) را به ترمینال دیکته میکنند. e\ یک escape sequence است که معنی ASCII 27 میدهد و 033\ نوشتار اوکتال همان است.
برای حفظ محتوای پیشین ترمینال از قابلیت Alt Screen استفاده میکنیم. این قابلیت که با یک کد انسی فعال میشود باعث میشود تا برنامهی ما در یک بافر جداگانه اجرا شود. این همان ویژگی است که برنامههایی همچون ویم از آن استفاده میکنند.
printf "\x1b[?1049h"
printf "\e[?25l"
printf "\x1b[?1049h"
setupTerm() {
printf "\x1b[?1049h"
printf "\e[?25l"
}
restoreTerm() {
printf "\x1b[?1049l"
printf "\x1B[?25h"
}
printHello() {
printf '\e[%d;%dH Hello World' $y $x
}
#!/usr/bin/env bash
shopt -s checkwinsize || return 1; (:;:)
x=$(( COLUMNS / 2 ))
y=$(( LINES / 2 ))
setupTerm() {
printf "\x1b[?1049h"
printf "\e[?25l"
}
restoreTerm() {
printf "\x1b[?1049l"
printf "\x1B[?25h"
}
printHello() {
printf '\e[%d;%dH Hello World' $y $x
}
setupTerm
trap 'restoreTerm;exit' SIGINT
printHello
while :; do
read -rsn3 key
clear
case "$key" in
$'\e[A') ((y--));;
$'\e[B') ((y++));;
$'\e[C') ((x++));;
$'\e[D') ((x--));;
esac
printHello
done
برای اینکه تابع restoreTerm هنگام خروج و دریافت SIGINT یعنی Ctrl-c اجرا شود، از trap استفاده میکنیم.
برای اطلاعات بیشتر man trap
ببنید.