فصل ‎23‎- جایگزینی پردازش

لوله‌کشی stdout یک فرمان به stdin فرمانی دیگر، یک شگرد قدرتمند است. اما، اگر به لوله‌کشی stdout چند فرمان، نیاز داشته باشید چطور؟ اینجاست که جایگزینی پردازش به کار می‌آید.

جایگزینی پردازش، خروجی یک پردازش (یا پردازش‌ها) را به stdin یک پردازش دیگر تغذیه می‌کند.

قالب

لیست فرمان محصور شده درون پرانتزها

‎>(command_list)‎

‎<(command_list)‎

جایگزینی پردازش، فایل‌های ‎/dev/fd/<n>‎ را برای فرستادن نتایج پردازش(های) داخل پرانتزها به یک پردازش دیگر به کار می‌برد. ‎[1]‎

Caution

هیچ فاصله‌ای میان ‎<‎ یا ‎>‎ و پرانتزها وجود ندارد. وجود فاصله یک پیغام خطا خواهد داد.

bash$ echo >(true)
/dev/fd/63

bash$ echo <(true)
/dev/fd/63

bash$ echo >(true) <(true)
/dev/fd/63 /dev/fd/62


bash$ wc <(cat /usr/share/dict/linux.words)
 483523  483523 4992010 /dev/fd/63

bash$ grep script /usr/share/dict/linux.words | wc
    262     262    3601

bash$ wc <(grep script /usr/share/dict/linux.words)
    262     262    3601 /dev/fd/63


‎Bash‎ یک لوله‌ با دو توصیف‌گر فایل --fIn و fOut--، ایجاد می‌کند.‏ stdin فرمان true را به fOut متصل می‌کند‎(dup2(fOut, 0))‎‎، سپس Bash یک شناسه ‎/dev/fd/fIn‎ به فرمان echo عبور می‌دهد. در سیستم‌های فاقد فایل‌های ‎/dev/fd/<n>‎، ممکن است Bash از فایل‌های موقت استفاده نماید. (سپاس، ‎S.C.‎)

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

bash$ comm <(ls -l) <(ls -al)
total 12
-rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
-rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
-rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh
        total 20
        drwxrwxrwx    2 bozo bozo     4096 Mar 10 18:10 .
        drwx------   72 bozo bozo     4096 Mar 10 17:58 ..
        -rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
        -rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
        -rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh

جایگزینی فرمان می‌تواند محتویات دو دایرکتوری را -- برای دیدن آن که کدام نام‌فایل در یکی هست اما در دیگری نیست -- مقایسه کند.

diff <(ls $first_directory) <(ls $second_directory)

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

read -a list < <( od -Ad -w24 -t u2 /dev/urandom )
# خواندن لیست اعداد تصادفی از ‎/dev/urandom‎، پردازش آنها با od
#+                      و تغذیه به ورودی استاندارد ‎read‎ . . .

#از اسکریپت مثال ‎insertion-sort.bash‎ اهدایی ‎JuanJo Ciarlante‎.

PORT=6881   #  bittorrent

#   پویش درگاه برای کسب اطمینان از آن که شرارتی در کار نباشد.
netcat -l $PORT | tee>(md5sum ->mydata-orig.md5) |
gzip | tee>(md5sum - | sed 's/-$/mydata.lz2/'>mydata-gz.md5)>mydata.gz

# Check the decompression:
  gzip -d<mydata.gz | md5sum -c mydata-orig.md5)
#‎MD5sum‎ نسخه اصلی ‎stdin‎ را وارسی می‌کند و مسائل فشردگی را تشخیص می‌دهد.

# این مثال به وسیله ‎Bill Davidsen‎ اهدا گردیده است.
#   (با ویرایش اندک به وسیله نگارنده راهنمای ‎ABS)‏‎.

cat <(ls -l)
#  مشابه با   ‎ls -l | cat‎

sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin)
#تمام فایل‌ها در سه دایرکتوری اصلی bin را لیست و بر حسب نام‌فایل مرتب می‌نماید.
#                         توجه کنید که سه دستور متفاوت به ‎sort‎ تغذیه می‌شوند.

 
diff <(command1) <(command2)        #      اختلاف خروجی فرمان را ارایه می‌کند.

tar cf >(bzip2 -c > file.tar.bz2) $directory_name
#‎tar cf /dev/fd/?? $directory_name‎ و ‎bzip2 -c > file.tar.bz2‎ را احضار می‌کند.
#
#به علت ویژگی سیستمِ ‎/dev/fd/<n>‎، لازم نیست لوله بین دو فرمان با نام باشد.
#
#   می‌تواند به صورت زیر شبیه‌سازی بشود.
#
bzip2 -c < pipe > file.tar.bz2&
tar cf pipe $directory_name
rm pipe
#          یا
exec 3>&1
tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > file.tar.bz2 3>&-
exec 3>&-


# با تشکر از ‎Stéphane Chazelas‎

این هم یک شیوه خنثی کردن مشکل یک echo لوله‌کشی شده به حلقه ‎while-read‎ در حال اجرا در یک پوسته فرعی.

مثال ‎23-1‎. تغییر مسیر بلوک کد بدون انشعاب زدن

#!/bin/bash
# wr-ps.bash:   حلقه ‎while-read‎ با جایگزینی پردازش.

#     این مثال به وسیله ‎Tomas Pospisek‎ اهدا گردیده.
#(توسط نگارنده راهنمای ABS کاملا ویرایش گردیده است.)

echo

echo "random input" | while read i
do
  global=3D": Not available outside the loop."
  # ...زیرا در یک پوسته فرعی اجرا می‌گردد.
done

echo "\$global (from outside the subprocess) = $global"
# (از بیرونِ پردازش فرعی) ‎$global =‎

echo; echo "--"; echo

while read i
do
  echo $i
  global=3D": Available outside the loop."
  # ... زیرا در یک پوسته فرعی اجرا نمی‌گردد.
done < <( echo "random input" )
#    ^ ^

echo "\$global (using process substitution) = $global"
# ورودی تصادفی
# ‎$global‎ (با استفاده از جایگزینی پردازش) ‎= 3D: Available outside the loop‎


echo; echo "##########"; echo



# و به همچنین . . .

declare -a inloop
index=0
cat $0 | while read line
do
  inloop[$index]="$line"
  ((index++))
  #          در یک پوسته فرعی اجرا می‌شود، بنابراین ...
done
echo "OUTPUT = "
echo ${inloop[*]}       #        ...چیزی منعکس نمی‌شود.


echo; echo "--"; echo


declare -a outloop
index=0
while read line
do
  outloop[$index]="$line"
  ((index++))
  #            در پوسته فرعی اجرا نمی‌شود، بنابراین ...
done < <( cat $0 )
echo "OUTPUT = "
echo ${outloop[*]}      # ...تمام اسکریپت منعکس می‌گردد.

exit $?

این یک مثال مشابه است.

مثال ‎23-2‎. تغییر مسیر دادن خروجی جایگزینی پردازش به درون یک حلقه.

#!/bin/bash
# psub.bash

# به وسیله ‎Diego Molina‎ ایجاد گردیده است (تشکر!).

declare -a array0
while read
do
  array0[${#array0[@]}]="$REPLY"
done < <( sed -e 's/bash/CRASH-BANG!/' $0 | grep bin | awk '{print $1}' )
#متغیر پیش‌فرض read یعنی ‎$REPLY‎ را توسط جایگزینی پردازش
#+      تنظیم می‌کند، سپس به داخل یک آرایه کپی می‌نماید.

echo "${array0[@]}"

exit $?

bash$ bash psub.bash
#!/bin/CRASH-BANG! done 

خواننده‌ای این مثال جالب جایگزینی فرمان را فرستاده است.

# قطعه اسکریپت اخذ شده از توزیع ‎SuSE‎

# --------------------------------------------------------------#
while read  des what mask iface; do
#چند فرمان ...
done < <(route -n)  
#    ^ ^  اولین ‎<‎ تغییر مسیر است، دومی جایگزینی پردازش است.

#برای تست این، بیاید آن را برای انجام کاری آماده کنیم.
while read  des what mask iface; do
  echo $des $what $mask $iface
done < <(route -n)  

#       خروجی:
#  Kernel IP routing table
#  Destination Gateway Genmask Flags Metric Ref Use Iface
# 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
# --------------------------------------------------------------#

#به طوریکه ‎Stéphane Chazelas‎ اشاره می‌کند،
#+    یک معادل آسان‌فهم‌تر آن عبارت است از:
route -n |
  while read des what mask iface; do   #متغیرها طبق خروجی لوله تنظیم می‌شوند.
    echo $des $what $mask $iface
  done  #این کد همان خروجی بالا را تحویل می‌دهد.
        # به هر حال، چنانکه ‎Ulrich Gayer‎ شرح می‌دهد، این معادل ساده
        #+شده، بواسطه حلقه ‎while‎ از یک پوسته فرعی استفاده می‌کند، و
        #+ بنابراین وقتی لوله خاتمه می‌یابد، متغیرها ناپدید می‌گردند.
	
# --------------------------------------------------------------#
	
#  اگر چه، ‎Filip Moritz‎ توضیح می‌دهد که تفاوت ظریفی بین دو
#+مثال فوق وجود دارد، همچنانکه در ادامه نمایش داده می‌شود.

(
route -n | while read x; do ((y++)); done
echo $y      #                  ‎$y‎ هنوز تنظیم نشده است

while read x; do ((y++)); done < <(route -n)
echo $y      #‎$y‎ دارای تعداد سطرهای خروجی ‎route -n‎ است
)

More generally spoken
(
: | x=x
 #   به نظر می‌رسد یک پوسته فرعی راه‌اندازی می‌شود، مانند
: | ( x=x )
#    درحالیکه، در
x=x < <(:)
#     پوسته فرعی راه‌اندازی نمی‌شود.
)

#            این موقع تجزیه csv و مشابه آن، سودمند است.
#    آنچه که قطعه کد اصلی SuSE به طور موثر انجام می‌دهد.

یادداشت‌ها

‎[1]‎

این دارای نتیجه همانند یک لوله بانام (فایل موقت) است، و در حقیقت در آغاز لوله‌های با نام در جایگزینی پردازش به کار رفتند.