آموزش‌های خط فرمانی

این وبلاگ تلاش می‌کند گامی در حد بضاعت در جهت آموزش خط فرمان و اسکریپت‌نویسی پوسته گنو-لینوکس بردارد.

آموزش‌های خط فرمانی

این وبلاگ تلاش می‌کند گامی در حد بضاعت در جهت آموزش خط فرمان و اسکریپت‌نویسی پوسته گنو-لینوکس بردارد.

نوشتن اسکریپت CGI !!


چگونه یک اسکریپت CGI بنویسم که پارامترها را بپذیرد؟

همواره شرایطی ماورای کنترل ما وجود دارد، که ما را به سمت انجام مواردی می‌راند، که اگر به عهده خودمان بود هرگز انجام آنها را انتخاب نمی‌کردیم. این مدخل FAQ یکی از آن موقعیت‌ها را تشریح می‌کند.

یک برنامه CGI می‌تواند با پارامترهایی که توسط مرورگر شبکه ارسال گردیده، فراخوانی بشود . دو روش(حداقل) برای فراخوانی برنامه CGI وجود دارد: شیوه "GET" و شیوه "POST" . در شیوه "GET" ، پارامترها در یک متغیر محیطی به نام ‎QUERY_STRING‎ برای برنامه CGI فراهم می‌شوند. پارامترها قالب تعاریف ‎KEY=VALUE‎ را می‌گیرند(یعنی ‎user=george‎)، با برخی کاراکترهایی که به هگزادسیمال کدگذاری شده‌اند، فاصله‌ها به عنوان علامت به‌اضافه کُد شده‌اند، و همه آنها با کاراکترهای & به یکدیگر متصل شده‌اند. در شیوه "POST" ، در عوض پارامترها در ورودی استاندارد فراهم می‌شوند.

البته اینک ما می‌دانیم که شما هرگز نمی‌خواهید یک اسکریپت CGI در Bash بنویسید. بنابراین برای اهداف مورد نظر این مدخل، ما فرض خواهیم نمود که تروریستها همسر و فرزندان شما را دزدیده‌اند و اگر شما تقاضای آنان را درنوشتن چنین اسکریپتی اجابت نکنید، آنها را شکنجه می‌کنند، ضرب و جرح می‌سازند، و می‌کشند، «یا وخیم‌تر».

(موقعیت «یا وخیم‌تر» شاید به وضوح چیزی مشابه مجبور کردن شما به استفاده از نرم‌افزارهای مایکروسافت باشد.)

بنابراین، برای یک متغیر معین ‎QUERY_STRING‎، شاید بخواهیم کلیدها(متغیرها) و مقادیر آنها را استخراج کنیم، به طوری که بتوانیم آنها را در اسکریپت به کار ببریم.

روش سریع، آسان، و خطرناک برای پردازش QUERY_STRING، تبدیل &ها به ;ها و سپس استفاده از فرمان eval برای انجام تخصیص آنها می‌باشد. به هر حال استفاده از eval به شدت دلسرد کننده است. این است که ما همیشه می‌گوییم، اگر راه دیگری برای انجام آن وجود دارد، از eval پرهیز نمایید.

روش خطرناک

#  cgi در خواندن رشته ورودی در
if [ "$QUERY_STRING" ]; then
  foo=$QUERY_STRING
else
  read foo
fi

# (‏می‌ماند برای  تمرین  خواننده)‎ ‎"&"‎ تبدیل مقداری رشته کُد شده و مواردی مانند‎

# روی رشته eval اجرای‎
eval $foo

# ‏را در یک فیلد فرم وب قرار ‎"/bin/rm -rf /"‎ کنار بنشینید و تماشاکنید، کاربر‎
# .نباشد می‌تواند به بخشی از سیستم‌فایل صدمه وارد کند root داده است، که حتی اگر‎
# .یک رشته خطرناک دیگر می‌ تواند بمب خوشه‌ای باشد

روش ایمن

به جای گفتن آنکه پوسته هر کُدِ فراهم شده توسط کاربر در پارامترها را اجرا کند، رویکرد بهتر استخراج هر زوج متغیر/مقدار، و تخصیص آنها در متغیرهای پوسته، به طور یک به یک، بدون اجرای آنها می‌باشد. این کار یک تخصیص متغیر غیر مستقیم نیاز دارد، که به معنی استفاده از بعضی ترفندکاری‌های مختص پوسته می‌باشد. ما این ترفند را با ترکیب دستوری Bash می‌نویسیم، تبدیل آن به پوسته ksh یا Bourne به عنوان تمرین واگذار می‌شود.

# Bash

# cgi  خواندن رشته ورودی در
if [ "$QUERY_STRING" ]; then
  foo=$QUERY_STRING
else
  read -r foo
fi

#  می‌باشد name=Fred+Flintstone&city=Bedrock شامل موردی مشابه foo‎
#  متصل شده‌اند رفتار می‌کند ‎&‎ که با key=value این مانند یک لیست از عبارتهای‎
#  تکرار روی عناصر لیست و انجام عمل تخصیص  هریک 

IFS='&'; set -f
for i in $foo; do
    declare "$i"
done
unset IFS

# یک متغیر پوسته با همان نام  خواهد شد CGI اکنون هر پارامتر‎
# .بهتر است شما بدانید نامها کدام هستند، چون آنها را پیگردی نمی‌کنیم‎
#  است. فاصله به عنوان + کُد شده است "urlencoded" هر متغیر  بازهم ‎
# هگزادسیمال است ‎xx‎ کُد شده‌اند که ‎%xx‎‎‎ اقلام مختلف به عنوان‎

# را به کار ببریم ‎"name"‎ فرض کنید می‌خواهیم پارامتری به نام‎

# .اول رمزگشایی فاصله‌ها‎

name=${name//+/ }

# ما ترفند دیگری برای انجام آن به کار می‌بریم ‎%xx‎  اکنون رمز گشایی کاراکترهای‎
# ‎تعویض می‌کنیم \x‎ را با  ‎%‎ اول تمام علائم ‎
# ‎ها می‌شود\xxx‎ را به کار می‌بریم برای آنکه موجب ارزیابی تمام ‎echo -e‎ دوم، دستور

name=${name//\%/\\x}
name=$(echo -e "$name")

# این کار را قبل از تکرار و تخصیص حلقه انجام ندادیم چون اگر این کار را می‌کردیم‎
# کُد شده (یا هر کاراکتر بدخواهانه)‏ است ‎&‎ آنوقت یک پارامتر که شامل یک کاراکتر‎
# .موجب اندوه بسیار خواهد شد. ما باید این کار را در اینجا انجام بدهیم ‎

# انجام بدهید ‎"name"‎ حالا هر کاری مایل هستید با

در حالیکه شاید این روش قدری کمتر واضح باشد، از مشکل امنیتی بزرگی اجتناب می‌کند که eval دارای آن است: اجرای هر فرمان دلخواهی که ممکن بود کاربر، علاقمند به ورود آن از طریق یک فُرم وِب باشد. به طور واضح این یک بهبود است.

در این نگارش هنوز کاستی‌هایی وجود دارد. برای مثال، ما برای تضمین معتبر یا ایمن بودن نام متغیر پوسته، هیچگونه اعتبارسنجی روی طرف چپ(نام متغیر) در هر زوج ‎key=value‎ انجام نمی‌دهیم. اگر کاربر یک ‎PATH=‎ را در یک پارامتر استعلام وارد کند چه؟

آرایه‌های انجمنی

حتی یک رویکرد بهتر، می‌تواند قرار دادن زوج‌های کلید\متغیر داخل یک آرایه انجمنی باشد. آرایه‌های انجمنی در ksh93 و ‎bash 4.0‎ معتبر هستند، اما در POSIX یا پوسته‌های Bourne خیر. آنهابرای نگهداری زوج‌های ‎key/value‎ طراحی شده‌اند که در آن کلیدها می‌توانند رشته‌های اختیاری باشند، بنابراین به نظر می‌رسد برای این کار مناسب هستند.

# Bash 4+

#  cgi خواندن رشته ورودی‎
if [ "$QUERY_STRING" ]; then
  foo=$QUERY_STRING
else
  read -r foo
fi

# .برقراری آرایه انجمنی برای نگهداری پارامترهای پرس وجو‎
declare -A q

# ‎key=value+%41%42%43‎ تکرار روی عناصر‎
# .جدا سازی کلید و کمیت، و انجام رمزگشایی کمیت‎
IFS='&'; set -f
for i in $foo; do
    IFS='=' read key value <<< "$i"

    # ‎هاbackslash‎ حذف  ‎--‎ اول پاک سازی :‎ مراحل رمزگشایی ‎
    # .دوم، علامت‌های بعلاوه تبدیل به فاصله می‌شوند‎
    # .می‌شوند ‎\x‎ سوم، علائم درصدتبدیل به ‎
    # .را موجب گردد printf چیزی باقی نمی‌گذارد که بتواند به طور غیر منتظره یک بسط ‎‎
    # .ها مال ما هستند و هیچ علامت درصد باقی نماندهbackslashe تمام ‎

    value=${value//\\/}
    value=${value//+/ }
    value=${value//\%/\\x}
    printf -v final -- "$value"
    q["$key"]="$final"
done
unset IFS

# .به کار ببریم q اکنون می‌توانیم پارامترها را از آرایه انجمنی با نام ‎
#  ‎${!q[*]}‎ اگر لیستی از کلیدها را لازم داشته باشیم، این است‎

در اینجا مرحله پاکسازی بینهایت مهم است. بدون آن اقدام احتیاطی، فرمان printf می‌تواند به واسطه یک رشته قالب‌بندی مهاجم، آسیب پذیر باشد. گزینه ‎printf -v varname‎ در هر نگارش از bash که از آرایه‌های انجمنی پشتبانی کند، معتبر است، بنابراین می‌توانیم از آن در اینجا استفاده کنیم. خیلی بیشتر از فراخوانی پوسته فرعی مؤثر است. همچنین از مشکلات بالقوه‌ ‎echo -e‎ در صورتی که اتفاقاً value موردی مانند ‎-n‎ باشد، اجتناب نموده‌ایم.

به طور تکنیکی، خصوصیات CGI چندین نمونه از یک کلید را در یک استعلام منفرد اجازه می‌دهد. برای مثال، ‎group=managers&member=Alice&member=Charlie‎ یک رشته کاملاً مشروع پرس وجو می‌باشد. هیچ یک از رویکردها در این صفحه این حالت را مدیریت نمی‌کند(حداقل نه به طریقی که ما احتمالاً آنرا روش صحیح در نظر بگیریم). خوشبختانه، غالباً اینطور نیست که شما بخواهید یک اسکریپت CGI مانند این بنویسید، و در هر حال، شما مجبور نیستید برای انجام این وظیفه از bash استفاده کنید.


CategoryShell

پرسش و پاسخ 92 (آخرین ویرایش ‎2010-04-16 23:58:26‎ توسط GreyCat)


متغیر COLUMNS و LINES در اسکریپت


من تلاش می‌کنم تعداد سطر و ستون ترمینال خود را دریافت کنم، اما متغیرهای COLUMNS و LINES همیشه تهی هستند

COLUMNS و LINES در وضعیت محاوره‌ای توسط BASH تنظیم می‌شوند، آنها به طور پیش‌فرض در یک اسکریپت در دسترس نیستند. در اکثر سیستم‌ها خودتان می‌توانید با ترمینال پرس و جو کنید:

unsup() { echo "Your system doesn't support retrieving $1 with tput.  Giving up." >&2; exit 1; }
COLUMNS=$(tput cols) || unsup cols
LINES=$(tput lines) || unsup lines

Bash به طور خودکار متغیرهای COLUMNS و LINES را موقعی که پوسته محاوره‌ای تغییر اندازه داده می‌شود، به هنگام می‌کند. اگر شما این متغیرها را در اسکریپت تنظیم می‌کنید و می‌خواهید موقعی که اندازه ترمینال تغییر می‌کند، آنها به هنگام شوند، یعنی به مجرد دریافت سیگنال SIGWINCH، می‌توانید خودتان یک trap تنظیم کنید:

trap 'COLUMNS=$(tput cols) LINES=$(tput lines)' WINCH

همچنین می‌توانید در سرآیند اسکریپت، پوسته را به عنوان محاوره‌ای تنظیم کنید:

#!/bin/bash -i
echo $COLUMNS

به هر حال، این مورد دارای اشکالاتی هست:

  • بررسی بواسطه گزینه ‎-i‎ به منظور تعیین آنکه آیا پوسته محاوره‌ای است، و سپس انصراف یا درست رفتار نکردن، برای اسکریپت‌ها خیلی غیر معمول نیست، اگرچه بهترین کار نمی‌باشد. هیچ روش کاملاً خالی از نقصی برای بررسی این مطلب، وجود ندارد، بنابراین بعضی از اسکریپت‌ها ممکن است به سبب آن ناموفق شوند.

  • اجرا با گزینه ‎-i‎ فایل ‎.bashrc‎ را منبع می‌کند، و گزینه‌های مختلفی از قبیل ‎job-control‎ را تنظیم می‌کند که شاید آثار جانبی ناخواسته داشته باشند.

اگر چه از لحاظ تکنیکی می‌توانید ‎-i‎ را در میان اسکریپت تنظیم کنید، اما تأثیری بر تنظیم COLUMNS و LINES ندارد -- ‎-i‎ باید ابتدا، موقعی که Bash احضار می‌شود برقرار باشد.

معمولاً Bash موقعی که ترمینال شما سیگنال SIGWINCH نشان دهنده تغییر اندازه را ارسال می‌کند،COLUMNS و LINES را به روز رسانی می‌کند. برخی ترمینالها ممکن است این کار را انجام ندهند، بنابراین اگر متغیرهای شما حتی موقعی که یک پوسته محاوره‌ای در حال اجرا است به روز رسانی نمی‌شوند، استفاده از ‎shopt -s checkwinsize‎ را امتحان کنید. این دستور باعث استعلام ترمینال توسط Bash پس از هر فرمان می‌شود، بنابراین تنها در صورتی که واقعاً لازم است از آن استفاده کنید.

البته، tput به ترمینال نیاز دارد. مطابق POSIX، اگر خروجی استاندارد tty نباشد، نتایج تعیین شده نیستند، و stdin استفاده نمی‌شود، اگر چه بعضی پیاده‌سازی‌ها ممکن است سعی کنند به هرحال از آن استفاده کنند. در لینوکس OpenBSD و Gentoo (و ظاهراً حداقل برخی لینوکس‌های دیگر)، دست کم یکی از stdout یا stderr باید tty باشد، وگرنه tput فقط بعضی مقادیر پیش‌فرض را برمی‌گرداند.

linux$ tput -S <<<$'cols\nlines' 2>&1 | cat
80
24

openbsd$ tput cols lines 2>&1 | cat
80
24

پرسش و پاسخ 91 (آخرین ویرایش ‎2013-01-11 22:11:44‎ توسط GreyCat)


درج متن در ابتدای فایل


چگونه می‌توانم متنی را در ابتدای یک فایل درج کنم(برعکسِ ‎>>‎

نمی‌توانید آنرا به تنهایی با تغییر مسیر bash انجام دهید، برعکسِ‎>>‎ وجود ندارد....

برای درج محتویات در ابتدای فایل، می‌توانید از یک ویرایشگر استفاده کنید، برای مثال ex:

ex file << EOF
0a
header line 1
header line 2
.
w
EOF

یا ed:

printf '%s\n' 0a "line 1" "line 2" . w | ed -s file

ex همچنین در صورت فقدان کاراکتر سطرجدید در انتهای فایل آن را به فایل اضافه می‌کند.

یا با استفاده از مواردی از این قبیل می‌توانید فایل را بازنویسی کنید:

{ echo line; cat file ;} >tmpfile && mv tmpfile file
echo line | cat - file > tmpfile && mv tmpfile file

برخی اشخاص اصرار دارند از چکش sed برای کوبیدن تمام پیچ‌ها استفاده کنند:

sed "1iTEXTTOPREPEND" filename > tmp &&
mv tmp filename

راه حل‌های بسیار دیگری نیز وجود دارد.


پرسش و پاسخ 90 (آخرین ویرایش ‎2011-03-15 17:02:32‎ توسط GreyCat)


مشکل خواندن فایل توام با اجرای ffmpeg


من در حال خواندن سطر به سطر یک فایل, و اجرای ssh یا ffmpeg هستم، فقط سطر اول پردازش می‌شود!

خواندن سطر به سطر فایل، اگر یک دستور در داخل حلقه نیز stdin را بخواند، می‌تواند فایل ورودی را تهی کند، برای مثال:

  # مثالی که کار نمی‌کند‎
  while IFS= read -r file; do
    ffmpeg -i "$file" -vcodec libxvid -acodec libfaac -ar 32000 "${file%.avi}".mkv
  done < <(find . -name '*.avi')

  # مثالی که کار نمی‌کند‎
  while read host; do
    ssh "$host" some command
  done <hostslist

در اینجا چه اتفاقی رخ می‌دهد؟ اجازه بدهید مثال اول راببینیم. read سطری از ورودی استاندارد(‎FD 0‎) می‌خواند،آن را در پارامتر file قرار می‌دهد، و بعد ffmpeg اجرا می‌شود. مانند هر برنامه دیگری که شما از BASH اجرا می‌کنید، ffmpeg نیز ورودی استاندارد را به ارث می‌برد، که بنا به دلایلی از آن می‌خواند. من نمی‌دانم چرا. اما در هر وضعیتی، موقعی که ‎ffmpeg از stdin‎ می‌خواند، تمام ورودی را از فرمان find جذب می‌کند، حلقه از گرسنگی می‌میرد.

این هم چگونه کارآمد ساختن آن:

  while IFS= read -r file; do
    ffmpeg -i "$file" -vcodec libxvid -acodec libfaac -ar 32000 "${file%.avi}".mkv </dev/null
  done < <(find . -name '*.avi')

به تغییر مسیر در سطر ffmpeg توجه نمایید: ‎</dev/null‎. مثال ssh می‌تواند به همین ترتیب ، یا با گزینه ‎-n‎ اصلاح بشود (حداقل با OpenSSH).

گاهی اوقات شاید کار کردن با آنچه از ورودی استاندارد خوانده می‌شود، با حلقه‌های بزرگ دشوار گردد، یا ممکن است برنامه موقعی که شما ‎</dev/null را به آن اضافه می‌کنید رفتارش را تغییر بدهد. در این وضعیت شما می‌توانید کاری کنید که فرمان read از توصیف‌گر فایل متفاوتی استفاده کند، که کمتر محتمل است یک برنامه تصادفی از آن بخواند:

  while read <&3 line; do
    ......
  done 3<file

یا از گزینه ‎-u‎ فرمان read استفاده کنید(POSIX نیست):

  # Bash
  while read -u 3 line; do
    ......
  done 3<file


CategoryShell

پرسش و پاسخ 89 (آخرین ویرایش ‎2013-05-17 20:58:31‎ توسط GreyCat)


حفظ تمام سطرهای فایل history


چطور می‌شود از فقدان هر یک از سطرهای تاریخچه اجتناب نمود؟

این روش برای آن طراحی شده تا به شما امکان بدهد ثبت وقایع کامل دوستانه‌ای از تمام فرمانهای اجرا شده توسط کاربر را ذخیره کنید، مقصود از آن حسابرسی امن فرمانها نمی‌باشد -صفحه امنیت bash در برابر پاکسازی تاریخچه را ببینید.

به طور پیش‌فرض، Bash تاریچه‌اش رافقط موقع خروج به روزرسانی می‌کند، و تاریخچه موجود را با یک نگارش جدیدتر بازنویسی می‌کند. این مطلب به دو دلیل شما را از داشتن یک فایل ثبت وقایع تاریخچه محروم می‌کند:

  • اگر کاربری در چندین نوبت به سیستم وارد شود(login)، رونویسی تضمین خواهد نمود که فقط آخرین پوسته تاریخچه‌اش هنگام خروج ذخیره خواهد شد.
  • اگر پوسته شما به طور غیرعادی خاتمه یابد - برای مثال به دلیل مشکلات شبکه، تغییرات فایروال، یا به علت اینکه kill گردیده است -تاریخچه‌ای نوشته نخواهد شد.

برای حل مشکل نخست، ما گزینه histappend پوسته را تنظیم می‌کنیم که باعث می‌شود تمام سطرهای تاریخچه جدید در فایل پیوست بشوند، و تضمین می‌شود که تاریخچه ورود(login)های متعدد یکدیگر را رونویسی نمی‌کنند.

برای ممانعت از مفقود شدن سطرهای تاریخچه در وضعیتی که Bash به طور غیرعادی خارج شود، لازم است مطمئن شویم که سطرها بعد از هر دستور نوشته می‌شوند. می توانیم از دستور داخلی پوسته به شکل ‎history -a‎ استفاده کنیم که باعث نوشته شدن فوری تمام سطرهای تاریخچه می‌گردد، و ما می‌توانیم با افزودن آن به متغیر ‎PROMPT_COMMAND‎ اجرای آن را خودکار نماییم. این متغیر محتوی فرمانی برای اجرا شدن قبل از نمایش هر اعلان فرمان جدید است، و بنابراین بعد از هر فرمان پوسته محاوره‌ای، اجرا می‌شود.

توجه نمایید که اجرای ‎'history -a'‎ بعد از هر فرمان تأثیر دوجانبه دارد:

  • یک لاگین جدید بلافاصله قادر خواهد بود تاریخچه اتصالهای موجود را مرور کند. بنابراین اگر می‌خواهید یک فرمان را در دو نشست اجرا نمایید، فرمان را اجرا کنید و سپس اتصال دوم را برقرار کنید و شما قادر خواهید بود فوراً فرمان را بازیابی کنید.
  • بیشتر از جهت منفی، تاریخچه فرمان پوسته‌های محاوره‌ای همزمان(برای یک کاربر معین)در هم پیچیده خواهند شد. بنابراین تضمین نمی‌شود که تاریخچه فرمان یک لیست متوالی از دستورات به همان ترتیبی باشد که در یک پوسته منفرد اجرا شده‌اند. اگر شما به جای یک فرمان منفرد، کل فایل تاریخچه را جهت جستجوی «بخشهای بسته‌بندی شده برای اجرای وظایف خاص» بازبینی نمایید، شاید آن را گیج کننده بیابید. این موضوع فقط در وضعیتی محتمل است که شما چند شخص در حال استفاده همزمان از یک حساب کاربری داشته باشید، که به هرحال ایده‌آل نیست.

برای انجام تمام این موارد، کد زیر را در فایل ‎~/.bashrc‎ خودتان به کار ببرید:

  •  HISTFILESIZE=400000000
     HISTSIZE=10000
     PROMPT_COMMAND="history -a"
     export HISTSIZE PROMPT_COMMAND
    
     shopt -s histappend

در کد فوق همچنین حداکثر تعداد سطرهای تاریخچه را که در حافظه ذخیره می‌شوند، افزایش داده‌ایم و هر محدودیتی برای خود فایل تاریخچه را حذف نمودیم. پیش‌فرض اینها 500 سطر است، که اگر شما کاربر فعالی هستید باعث می‌شود به سرعت شروع کنید به از دست دادن سطر فرمانهای تاریخچه. با تنظیم کردن HISTFILESIZE به یک کمیت درشت، یک فایل به اندازه کافی بزرگ را به طوری که عملاً نامحدود است تأمین می‌کنیم - و با تنظیم ‎$HISTSIZE‎، تعداد این سطرها که در حافظه نگهداری می‌شوند را محدود نموده‌ایم. متأسفانه، bash کل فایل تاریخچه را قبل از آنکه کوتاه شده آن به اندازه ‎$HISTSIZE‎ را به حافظه کپی کند، می‌خواند - بنابراین اگر فایل تاریخچه فرمان شما خیلی بزرگ شود، زمان شروع اولیه bash شما می‌تواند به طور رنجش‌آوری بالا برود. حتی بدتر، بارگیری فایل تاریخچه بزرگ و بعد کوتاه‌سازی آن به اندازه ‎ $HISTSIZE ‎ منجر به نفخ منابع مورد استفاده می‌شود، bash حافظه بسیار بیشتری از وقتی که فایل تاریخچه فقط به اندازه ‎$HISTSIZE‎ سطر باشد، مصرف می‌کند. بنابراین اگر انتظار دارید فایل تاریخچه شما خیلی بزرگ شود، برای مثال بالای ‎20,000‎ سطر، باید به طور متناوب آنرا بایگانی کنید. بایگانی فایلهای تاریخچه در پایین را ببینید.

‎PROMPT_COMMAND‎ ممکن است قبلاً در تنظیمات شما به کار رفته باشد، برای مثال شامل کدهای کنترل برای به روزرسانی یک نوار نمایش XTerm با اعلان فرمان شما. اگر مال شما از قبل در استفاده است می‌توانید به این طریق به آن پیوست کنید: ‎PROMPT_COMMAND="${PROMPT_COMMAND:-:} ; history -a"

همچنین شاید بخواهید متغیرهای HISTIGNORE و HISTCONTROL را برای کنترل آنچه ذخیره می‌شود، برای مثال حذف سطرهای تکراری، به کار ببرید - به هر حال انجام آن شما را از دیدن آن که چندبار یک فرمان معین توسط کاربری اجرا شده باز می‌دارد، و به طور دقیق وقتی( HISTTIMEFORMAT نیز برقرار شده باشد).

سرانجام، توجه کنید که به علت اجرای PROMPT_COMMAND درست قبل از اینکه اعلان فرمان چاپ شود، ممکن است شما آخرین سطر فرمان را در صورتی که پوسته در اثنای انجام این فرمان فسخ بشود، از دست بدهید. به عنوان یک مثال، ملاحظه کنید: ‎this_cmd_is_never_written_to_history ; kill -9 $$

فشرده‌سازی فایلهای تاریخچه

نتیجه عمل فوق، یک فایل تاریخچه با مقدار بسیاری فرمانهای تکراری است. پیوست نمودن تاریخچه باعث می‌شود فایل تاریخچه شما به وسیله همه تاریخچه‌های بارگذاری شده پوسته در هر نوبت، رشد کند.

به طور مهمتر، مطلب عمده‌ای که با حساسیت نسبت به تاریخچه برای ما اهمیت دارد آن است که قادر به یافتن دستوراتی باشیم که قبلاً اجرا شده‌اند. اسکریپت زیر تمام دستوراتی که از قبل در فایل تاریخچه هستند را از آن حذف می‌کند، در حالیکه ترتیب دستورات دست نخورده حفظ می‌گردد، به طریقی که آخرین دستورات اجرا شده در انتهای فایل باقی می‌مانند (یعنی حفظ آخرین وقوع یک فرمان، نه اولین مورد).

    awk 'NR==FNR && !/^#/{lines[$0]=FNR;next} lines[$0]==FNR' "$HISTFILE" "$HISTFILE" > "$HISTFILE.compressed" &&
    mv "$HISTFILE.compressed" "$HISTFILE"

پس از چند ماه، این اسکریپت فایل تاریخچه‌ام را از 761474 به 2349 سطر فشرده نمود.

بایگانی فایلهای تاریخچه

یکبار که شما این روشها را فعال کنید، متوجه خواهید شد که تاریخچه bash خیلی بیشتر ارزشمند می‌شود، میسر نمودن فراخوانی هر فرمانی که هر زمانی اجرا کرده‌اید. همینطور، شما باید مطمئن شوید که فایل(های) تاریخچه‌تان به پشتیبان‌های مقرر شما ضمیمه می‌گردد.

همچنین ممکن است برای ممانعت از بارگیری تاریخچه کامل در حافظه، توسط هر پوسته bash جدید، بخواهید بایگانی منظم فایل تاریخچه خود را فعال نمایید. با یک فایل تاریخچه شامل ‎10,000‎ مدخل، bash در ‎Solaris 10‎ تقریباً از ‎5.5MB‎ حافظه استفاده می‌کند، با تأخیر غیرقابل ارزیابی در شروع اولیه( من فرض ‌کنم با ‎$HOME‎ روی یک دیسک محلی؟ -- GreyCat). با تاریخچه‌ای به اندازه ‎100,000‎ مدخل این به ‎10MB‎ با تأخیر قابل ملاحظه ‎3-5‎ ثانیه در زمان شروع می‌رسد. بایگانی متناوب برای پاک کردن قدیمی‌ترین سطرهای گزارش و برای اجتناب از هرز دادن منابع قابل توصیه است، مخصوصاً اگر RAM بسیار ارزشمند باشد. (بزرگترین ‎~/.bash_history‎ من بعد از ‎1.5‎ ماه ‎7500‎ مدخل است.)

این امر به بهترین وجه از طریق ابزاری که می‌تواند بخشی از فایل را بایگانی کند انجام شده است. یک اسکریپت ساده برای انجام این کار اینطور خواهد بود:

  •  #!/bin/bash
     umask 077
     max_lines=10000
    
     linecount=$(wc -l < ~/.bash_history)
    
     if (($linecount > $max_lines)); then
             prune_lines=$(($linecount - $max_lines))
             head -$prune_lines ~/.bash_history >> ~/.bash_history.archive \
                    && sed -e "1,${prune_lines}d"  ~/.bash_history > ~/.bash_history.tmp$$ \
                    && mv ~/.bash_history.tmp$$ ~/.bash_history
     fi

این اسکریپت سطرهای کافی از بالای فایل تاریخچه را برای کوتاه کردن آن به اندازه X سطر پاک می‌کند، باقیمانده را در فایل ‎~/.bash_history.archive‎ درج می‌کند. این از توانایی هرس‌کردن HISTFILESIZE تقلید می‌کند، اما باقیمانده را به جای حذف کردن، بایگانی می‌کند - تضمین می‌کند شما همواره می‌توانید تاریخچه گذشته را با ‎grep ~/.bash_history*‎ جستجو نمایید.

چنین اسکریپتی می‌تواند به طور شبانه یا هفتگی از crontab شخصی شما برای فعال کردن تهیه بایگانی دوره‌ای به کار برود. توجه نمایید که چندین کاربر را مدیریت نمی‌کند و فقط تاریخچه کاربر جاری را بایگانی خواهد نمود - توسعه دادن آن جهت اجرا برای تمام کاربران(همچون root) به عنوان تمرین برای خواننده باقی گذاشته شد.


CategoryShell

پرسش و پاسخ 88 (آخرین ویرایش ‎2013-07-21 20:03:09‎ توسط ppp091138132034)