Bash میں $() اور backticks: command substitution کا فرق
جانیں کہ $(...) میں cd اور variables کیوں ختم ہو جاتے ہیں، backticks کی nesting کیوں مشکل ہے، اور process substitution سے shell state کیسے برقرار رہتی ہے۔
Bash میں command substitution کیا کرتی ہے
Bash command substitution $(command) کو اس command کے standard output پر بھیجے گئے متن سے بدل دیتی ہے۔ Backticks والی پرانی syntax بھی یہی کام کرتی ہے۔ جو باتیں صارفین کو حیران کرتی ہیں، ان کی بنیاد دو حقائق پر ہے: command ایک الگ process میں چلتی ہے جسے subshell کہتے ہیں، اور اس کے output کے آخر میں موجود ہر newline حذف کر دی جاتی ہے۔
mkdir -p /tmp/subst-demo
cd /tmp/subst-demo
printf 'alpha\nbeta\ngamma\n' > three.txt
count=$(wc -l < three.txt)
echo "$count"3یہی اس feature کا مکمل طریقہ کار ہے۔ wc -l نے 3 کے بعد newline کے ساتھ output دیا، newline حذف ہو گئی، اور count میں وہ دو characters محفوظ ہو گئے جن کی آپ کو ضرورت تھی۔ یاد رکھیں کہ wc -l < three.txt صرف ایک عدد output کرتا ہے، کیونکہ standard input پڑھتے وقت GNU wc کے پاس ظاہر کرنے کے لیے کوئی filename نہیں ہوتا۔ اس کے بجائے wc -l three.txt لکھیں، تو آپ 3 three.txt capture کریں گے۔ یہ ایک مختلف string ہے اور بعد میں ناکام ہونے والی arithmetic کی عام وجہ بنتی ہے۔
اس guide کا باقی حصہ اس رویے کی وضاحت کرتا ہے جس کی عموماً کسی کو توقع نہیں ہوتی، کیونکہ subshell ایک الگ process ہوتا ہے، اور الگ process اس shell کو تبدیل نہیں کر سکتا جس میں آپ command لکھ رہے ہوتے ہیں۔
$() استعمال کریں اور backticks کا استعمال ترک کریں
دونوں صورتیں درست ہیں۔ $() POSIX میں شامل ہے، اس لیے dash، ash اور busybox sh سب اسے support کرتے ہیں۔ backticks لکھنے کی portability کے لحاظ سے اب کوئی وجہ باقی نہیں رہی، اور انہیں استعمال نہ کرنے کی 2 واضح وجوہات ہیں۔
Backticks کو nest نہیں کیا جا سکتا
echo "$(echo "$(echo hi)")"
echo "`echo `echo hi``"hi
echo hiدوسری لائن نے echo hi کے literal الفاظ print کیے۔ shell اگلے unescaped backtick تک آگے scan کرتا ہے، اس لیے آپ کے لکھے ہوئے دوسرے backtick نے پہلے backtick کو close کر دیا۔ اصل میں چلنے والی command echo تھی، جس میں کوئی argument نہیں تھا۔ اس نے ایک خالی لائن print کی، اور پھر اس کا newline strip ہو کر مکمل طور پر ختم ہو گیا۔ echo hi کے الفاظ plain text کے طور پر باقی رہ گئے، اور backticks کے آخری جوڑے نے ایک empty command چلائی۔
Backticks کو nest کرنے کے لیے ہر inner backtick کو escape کرنا پڑتا ہے:
echo "`echo \`echo hi\``"hiہر اضافی level پر escaping دوبارہ double ہو جاتی ہے۔ $() میں اس کی کوئی ضرورت نہیں، کیونکہ parser delimiter character تلاش کرنے کے بجائے parentheses کو match کرتا ہے۔
Command چلنے سے پہلے Backticks آپ کے backslashes تبدیل کرتے ہیں
echo "$(echo 'a\\b')"
echo "`echo 'a\\b'`"a\\b
a\bاسی inner command نے مختلف output پیدا کیا۔ backticks کے اندر shell inner text کو parse کرنے سے پہلے backslash escaping کی ایک layer ہٹا دیتا ہے، اس لیے single quotes نے کسی چیز کو protect نہیں کیا۔ $() کے اندر parentheses کے درمیان موجود text کو ordinary script کے طور پر parse کیا جاتا ہے، اس لیے single quotes توقع کے مطابق کام کرتے ہیں۔ یہ مسئلہ خاص طور پر sed اور awk one-liners میں نمایاں ہوتا ہے، جہاں ایک غائب backslash working pattern کو خاموشی سے مختلف pattern میں بدل دیتا ہے۔
$() کے اندر quoting بھی نئے سرے سے شروع ہوتی ہے، اس لیے آپ double quotes کو escape کیے بغیر nest کر سکتے ہیں:
path=/etc/nginx/nginx.conf
echo "$(dirname "$path")"/etc/nginxbacktick کے equivalent میں $path کے گرد \" درکار ہوتے ہیں۔ ہر escape غلطی کا ایک ممکنہ مقام ہے۔
cd کا $() کے اندر استعمال آپ کے shell کو کیوں نہیں بدلتا
کیونکہ $(...) ایک نیا process بناتا ہے۔ subshell کو آپ کے variables اور working directory کی نقل ملتی ہے۔ یہ اپنی نقل تبدیل کرتا ہے، کچھ output دکھاتا ہے، اور ختم ہو جاتا ہے۔ یہ نقل بھی اسی کے ساتھ ختم ہو جاتی ہے۔
cd /tmp/subst-demo
pwd
target=$(cd /etc && pwd)
echo "$target"
pwd/tmp/subst-demo
/etc
/tmp/subst-demoکچھ بھی ناکام نہیں ہوا۔ cd کامیابی سے چلا، اور subshell کے اندر pwd نے واقعی /etc دکھایا۔ اس تبدیلی کے واپس آنے کا کوئی راستہ نہیں، کیونکہ subshell اپنے parent کو صرف standard output اور exit status دیتا ہے۔
Variable assignments بھی اسی طرح کام کرتے ہیں:
count=0
msg=$(count=99; echo "inside: $count")
echo "$msg"
echo "outside: $count"inside: 99
outside: 0اسی اصول کی ایک زیادہ عام صورت pipeline کے ساتھ سامنے آتی ہے، substitution کے ساتھ نہیں:
n=0
cat three.txt | while read -r line; do n=$((n+1)); done
echo "$n"0Pipeline کا ہر stage اپنے subshell میں چلتا ہے، اس لیے while loop نے n کی ایک نقل میں اضافہ کیا اور پھر ختم ہو گیا۔ Pipe کو redirect سے تبدیل کریں، تو loop آپ کے shell میں چلے گا:
n=0
while read -r line; do n=$((n+1)); done < three.txt
echo "$n"3Bash shopt -s lastpipe کے ذریعے pipeline کا آخری stage موجودہ shell میں چلا سکتا ہے، لیکن صرف اس وقت جب job control بند ہو۔ Interactive shell میں یہ کبھی درست نہیں ہوتا۔ Redirect استعمال کریں۔
پرومپٹ کہاں چلا گیا؟ $() کے اندر interactive commands
Command substitution standard output کو ایک pipe میں بھیجتی ہے، جبکہ standard input کو جوں کا توں رہنے دیتی ہے۔ جو program اپنا سوال standard output پر دکھا کر جواب کا انتظار کرتا ہے، اس کا سوال غائب ہو جاتا ہے، انتظار ختم نہیں ہوتا۔ terminal منجمد دکھائی دیتا ہے۔
ask() { printf 'Username: '; read -r u; printf '%s\n' "$u"; }
askUsername: deploy
deployپہلی لائن پر موجود deploy وہ متن ہے جو آپ نے ٹائپ کیا تھا۔ اب اسی function کو substitution کے اندر چلائیں:
v=$(ask)deployصرف آپ کی اپنی keystrokes ظاہر ہوتی ہیں۔ انہیں program کے بجائے terminal driver echo کرتا ہے۔ پرومپٹ کسی اور جگہ چلا گیا:
echo "$v"Username: deployاب پرومپٹ variable کے اندر موجود ہے اور جواب کے ساتھ جڑ گیا ہے، کیونکہ $() نے function کے standard output پر لکھا گیا تمام متن capture کر لیا۔ آپ کی typing پھر بھی read تک پہنچی، کیونکہ standard input کو چھوا نہیں گیا تھا۔ یہ اس bug report کی واضح علامت ہے جس میں کہا جاتا ہے: "میرا script hang ہو جاتا ہے اور کچھ print نہیں کرتا۔"
کچھ tools اپنے prompts کو standard error یا براہ راست /dev/tty پر لکھتے ہیں تاکہ وہ اس صورت میں بھی دکھائی دیں۔ بہت سے tools ایسا نہیں کرتے۔ اگر کسی function کو substitution کے اندر ہی رہنا ضروری ہو تو اس کا prompt خود standard error پر بھیجیں:
ask() { printf 'Username: ' >&2; read -r u; printf '%s\n' "$u"; }
v=$(ask)
echo "$v"Username: deploy
deploystandard error capture نہیں ہوتا، اس لیے prompt آپ کے terminal تک پہنچ جاتا ہے اور صرف جواب v میں محفوظ ہوتا ہے۔
ls اندر $() مختلف کیوں دکھائی دیتا ہے؟
کیونکہ ls file descriptor 1 پر isatty کو کال کرتا ہے اور جواب کے مطابق اپنے output format کو تبدیل کرتا ہے۔ prompt پر یہ descriptor آپ کا terminal ہوتا ہے، اس لیے ls ناموں کو columns میں ایک ہی line پر پھیلا دیتا ہے۔ substitution کے اندر یہ pipe ہوتا ہے، اس لیے ls ہر line میں ایک نام دکھاتا ہے۔
mkdir -p /tmp/tty-demo
cd /tmp/tty-demo
touch alpha beta delta gamma
echo "$(ls)"alpha
beta
delta
gammaیہی check grep --color=auto میں رنگ بند کر دیتا ہے اور git میں pager بھی بند کر دیتا ہے۔ یہ ایک feature ہے۔ اس کا مطلب ہے کہ script کو stable machine-readable output ملتا ہے اور اسے اس کے لیے الگ سے درخواست نہیں کرنی پڑتی۔
اس سے pipelines کے بارے میں پوچھا جانے والا ایک سوال بھی واضح ہو جاتا ہے۔ prompt پر ls | sort اور $(ls | sort) ایک ہی چیز print کرتے ہیں، کیونکہ دونوں صورتوں میں ls کے output پر pipe موجود تھی۔ substitution کے اندر جو چیز تبدیل ہوتی ہے، وہ pipeline کا last stage ہے۔ sort terminal کی موجودگی کبھی check نہیں کرتا، اس لیے اس کا output کبھی تبدیل نہیں ہوتا۔ pipeline کے آخر میں terminal-aware command رکھیں تو output تبدیل ہو جاتا ہے۔ اسی لیے جس pipeline کو آپ نے براہِ راست دیکھ کر test کیا ہو، $() میں wrap کرتے ہی اس کا behavior مختلف ہو سکتا ہے۔
اس سے ایک اہم تنبیہ سامنے آتی ہے: script میں ls کے output کو parse نہ کریں، خواہ یہ آسان دکھائی دیتا ہو۔ Filenames میں spaces اور newlines شامل ہو سکتے ہیں۔ glob استعمال کریں، یا find -print0 کو read -d '' کے ساتھ استعمال کریں۔
کمانڈ substitution کے آخر میں موجود نئی لائنیں خاموشی سے ختم کر دیتی ہے
کمانڈ substitution آؤٹ پٹ کے آخر میں موجود ہر نئی لائن ختم کر دیتی ہے۔ صرف آخری نئی لائن نہیں، بلکہ سب نئی لائنیں۔
cd /tmp/subst-demo
printf 'hello\n\n\n' > blanks.txt
wc -c < blanks.txt
v=$(cat blanks.txt)
printf '%s' "$v" | wc -c8
5فائل میں hello اور اس کے بعد تین نئی لائنیں موجود ہیں، اس لیے اس کا سائز 8 bytes ہے۔ متغیر میں hello موجود ہے، اس لیے اس کا سائز 5 bytes ہے۔ تین bytes بغیر کسی warning کے ختم ہو گئے۔
یہ stripping جان بوجھ کر کی جاتی ہے اور عموماً مفید ہوتی ہے۔ اسی کی وجہ سے stamp=$(date -u +%Y%m%dT%H%M%SZ) ایسا قابلِ استعمال filename fragment بناتا ہے جس میں line break شامل نہیں ہوتا۔ اسی لیے یہ pattern scheduled restic backup script جیسی جگہ پر محفوظ ہے:
stamp=$(date -u +%Y%m%dT%H%M%SZ)
printf 'backup-%s.tar.gz\n' "$stamp"backup-20260804T031500Z.tar.gzآپ کا timestamp مختلف ہوگا۔ اہم بات یہ ہے کہ نام ایک ہی لائن پر ہو۔
اس کا نقصان یہ ہے کہ فائل کے exact bytes منتقل کرنے کے لیے آپ $() استعمال نہیں کر سکتے۔ اگر آخری نئی لائنیں محفوظ رکھنی ہوں تو substitution کے اندر ایک sentinel character شامل کریں اور بعد میں اسے ہٹا دیں:
v=$(cat blanks.txt; printf x)
v=${v%x}
printf '%s' "$v" | wc -c8x نئی لائنوں کے بعد موجود ہے، اس لیے strip کرنے کے لیے آخر میں کوئی نئی لائن باقی نہیں رہتی۔ اس کے بعد ${v%x} sentinel کو ہٹا دیتا ہے اور اصل bytes برقرار رہتے ہیں۔
اس سے متعلق دو مزید باتیں ہیں۔ پوری فائل پڑھنے کے لیے v=$(<blanks.txt)، cat چلائے بغیر یہی کام کرتا ہے، کیونکہ bash خود فائل کھولتا ہے۔ یہ بھی آخر کی نئی لائنیں اسی طرح strip کرتا ہے۔ اس کے برعکس here-strings ایسی نئی لائن شامل کر دیتی ہیں جو آپ نے لکھی نہیں تھی:
wc -c <<< 'abc'4نتیجہ quote کریں، ورنہ bash اسے split اور glob کرے گا
بغیر quotes والی substitution پہلے word splitting اور پھر pathname expansion سے گزرتی ہے۔ quotes والی substitution دونوں مراحل سے نہیں گزرتی۔
printf 'a b\tc\nd\n' > words.txt
echo $(cat words.txt)
echo "$(cat words.txt)"a b c d
a b c
dبغیر quotes کے، bash نے IFS میں موجود characters کی بنیاد پر output کو split کیا۔ بطور default یہ characters space، tab اور newline ہوتے ہیں، اور echo نے پھر چار حصوں کو single spaces کے ساتھ جوڑ دیا۔ quotes کے ساتھ، text ایک ہی word کے طور پر منتقل ہوا، جبکہ tab اور اندرونی newline برقرار رہے۔
Globbing زیادہ خطرناک مرحلہ ہے:
mkdir -p /tmp/glob-demo
cd /tmp/glob-demo
touch one.txt two.txt
printf '*\n' > pattern.txt
p=$(cat pattern.txt)
echo $p
echo "$p"one.txt pattern.txt two.txt
*Assignment خود محفوظ تھا، کیونکہ assignments میں word splitting یا globbing نہیں ہوتی۔ نقصان echo $p پر ہوا، جہاں * کو current directory کے خلاف expand کیا گیا۔ اگر کوئی script config file سے pattern پڑھتی ہے اور quotes بھول جاتی ہے تو وہ دستیاب ہر file پر کارروائی کر سکتی ہے۔ ہر expansion کو quote کریں، اس سے bugs کی پوری یہ class ختم ہو جاتی ہے۔ Quotes صرف اس وقت چھوڑیں جب آپ واقعی splitting چاہتے ہوں، اور ایسا کم ہی ہوتا ہے۔
local x=$(cmd) ہمیشہ 0 کیوں واپس کرتا ہے؟
کیونکہ local خود ایک command ہے، اور $?، local کے status کی اطلاع دیتا ہے، نہ کہ اس میں شامل substitution کے status کی۔
check_bad() { local out=$(false); echo "status: $?"; }
check_badstatus: 0false نے 1 کے ساتھ exit کیا، local variable declare کرنے میں کامیاب رہا، اور 1 ضائع ہو گیا۔ declare، export، typeset اور readonly بھی اسی طرح کام کرتے ہیں۔ set -e بھی اسے نہیں پکڑے گا، کیونکہ shell کے نقطۂ نظر سے کچھ بھی failed نہیں ہوا۔
Declaration کو assignment سے الگ کریں:
check_good() { local out; out=$(false); echo "status: $?"; }
check_goodstatus: 1Top level پر سادہ assignment پہلے ہی اپنی آخری command substitution کا status واپس کرتی ہے:
out=$(exit 3)
echo $?3یہ خاص طور پر اس health check میں اہم ہے جو systemd service اور timer کے تحت چل رہا ہو۔ masked exit code کی وجہ سے unit ہر run پر success report کرتی ہے، جبکہ وہ کام کبھی نہیں ہوا جس کی تصدیق اسے کرنی تھی۔
وہ in-shell متبادل جو حقیقت میں آپ چاہتے ہیں
زیادہ تر لوگ $(...) اس لیے استعمال کرتے ہیں کہ انہیں کسی variable میں data درکار ہوتا ہے۔ لیکن اکثر اصل ضرورت input کی ہوتی ہے، capture کی نہیں۔ یہ چار طریقے موجودہ shell میں state برقرار رکھتے ہیں۔
loop پر redirect
cd /tmp/subst-demo
while read -r line; do printf 'got: %s\n' "$line"; done < three.txtgot: alpha
got: beta
got: gammaInput کے لیے کوئی نیا process نہیں بنایا جاتا، اس لیے loop body میں set کیے گئے values loop کے بعد بھی برقرار رہتے ہیں۔
Process substitution
while read -r line; do printf 'got: %s\n' "$line"; done < <(sort -r three.txt)got: gamma
got: beta
got: alpha<(command) آپ کو ایک path فراہم کرتا ہے، مثلاً /dev/fd/63، جسے پڑھنے پر command کا output ملتا ہے۔ Command پھر بھی اپنے الگ process میں چلتی ہے۔ while loop ایسا نہیں کرتا، اور یہی اس طریقے کا مقصد ہے۔ < <( میں space ضروری ہے: <<( کو here-document کے آغاز کے طور پر پڑھا جاتا ہے اور یہ parse نہیں ہوگا۔ Process substitution، bash کا feature ہے، اس لیے Ubuntu یا Debian پر #!/bin/sh والی script، dash کے تحت چلے گی اور ناکام ہوگی۔ #!/bin/bash استعمال کریں۔
Here-strings
read -r first rest <<< 'alpha beta gamma'
echo "$first"
echo "$rest"alpha
beta gamma<<< ایک string کو command کے standard input میں بھیجتا ہے۔ read آپ کے shell میں چلتا ہے، اس لیے دونوں variables اسی جگہ set ہوتے ہیں جہاں آپ انہیں استعمال کر سکتے ہیں۔ read -r a b <<< "$(some-command)" output کی ایک line سے دو fields حاصل کرنے کا معمول کا طریقہ ہے۔
پوری files کے لیے mapfile
mapfile -t lines < three.txt
echo "${#lines[@]}"
echo "${lines[1]}"3
betamapfile، جسے readarray بھی لکھا جاتا ہے، موجودہ shell میں ایک file کو array میں پڑھتا ہے۔ -t ہر element کے آخر میں موجود newline ہٹا دیتا ہے۔ اس کے لیے bash 4 یا اس کے بعد کا ورژن درکار ہے، اور Ubuntu 24.04 کے ساتھ bash 5.2 آتا ہے، اس لیے یہ ہر موجودہ server image میں دستیاب ہے۔
اسکرپٹ کو حتمی شکل دینے سے پہلے چیک لسٹ
$(command)لکھیں، اور اسے"$(command)"کے طور پر quote کریں، الاّ یہ کہ آپ جان بوجھ کر splitting چاہتے ہوں۔- یہ فرض کریں کہ آخر میں موجود نئی سطریں ختم ہو چکی ہیں۔ اگر انہیں برقرار رکھنا ضروری ہو تو sentinel character شامل کریں۔
- interactive commands کو substitutions سے باہر رکھیں، یا ان کے prompts کو standard error پر بھیجیں۔
- جب
out=$(command)کا exit status اہم ہو توlocal outکو اپنی الگ سطر پر لکھیں۔ - input سے variables set کرنے کے لیے pipe کے بجائے redirect یا process substitution استعمال کریں۔
یہ صورتیں ان ابتدائی چھوٹے scripts میں سامنے آتی ہیں جو لوگ نیا VPS قائم کرتے وقت عموماً لکھتے ہیں، اور ان کی failures خاموش رہتی ہیں۔ ایسا backup script جو prompt کو filename میں محفوظ کر لے، یا ایسا health check جو exit code کو چھپا دے، مسلسل success رپورٹ کرتا رہتا ہے۔ جب آپ اسی script کو متعدد سرورز پر چلاتے ہیں تو نقصان بڑھ جاتا ہے، کیونکہ جو output آپ پہلے نہیں پڑھتے تھے، اب وہ بیس machines پر بھی ایسا output ہے جسے آپ نہیں پڑھتے۔
FAQ
$() کے اندر cd کرنے سے موجودہ directory کیوں تبدیل نہیں ہوتی؟
$(...) اپنی command کو subshell میں چلاتا ہے۔ یہ ایک الگ process ہوتا ہے جس کے پاس working directory اور variables کی الگ نقول ہوتی ہیں۔ cd اس نقل کو تبدیل کرتا ہے، پھر process ختم ہو جاتا ہے اور یہ نقل ضائع ہو جاتی ہے۔ subshell صرف اپنا standard output اور exit status واپس کر سکتا ہے، اس لیے directory کی تبدیلی parent shell تک پہنچنے کا کوئی طریقہ نہیں ہوتا۔ اگر آپ کو directory درکار ہو تو اسے target=$(cd /etc && pwd) کے ذریعے حاصل کریں اور "$target" استعمال کریں۔ اگر آپ چاہتے ہیں کہ shell خود منتقل ہو تو substitution کے بغیر cd براہِ راست چلائیں۔
bash میں $() اور backticks میں کیا فرق ہے؟
سادہ commands کے لیے دونوں ایک ہی نتیجہ دیتے ہیں، لیکن دو اہم فرق ہیں۔ $() براہِ راست nested ہو سکتا ہے کیونکہ parser parentheses کو match کرتا ہے، جبکہ backticks میں nesting کی ہر سطح کے لیے escaped backtick درکار ہوتا ہے۔ backticks اندرونی command کی parsing سے پہلے backslash escaping کی ایک سطح بھی ہٹا دیتے ہیں، اس لیے ` echo 'a\\b' prints a\b while $(echo 'a\\b') prints a\\b. $() is in POSIX and works in dash and busybox sh`، لہٰذا backticks کے حق میں portability کی کوئی دلیل نہیں ہے۔
جب command سوال پوچھتی ہے تو میری script prompt کے بغیر کیوں رک جاتی ہے؟
Command substitution standard output کو pipe میں redirect کرتی ہے، لیکن standard input کو آپ کے terminal سے منسلک رہنے دیتی ہے۔ جو program اپنا prompt standard output پر لکھتا ہے، اس کا prompt variable میں capture ہو جاتا ہے، جبکہ اس کے پیچھے موجود read آپ کے جواب کا انتظار کرتا رہتا ہے۔ terminal صرف وہ characters دکھاتا ہے جو آپ ٹائپ کرتے ہیں اور جنہیں terminal driver echo کرتا ہے۔ سوال کو substitution سے باہر منتقل کریں، یا printf 'Username: ' >&2 کے ذریعے prompt کو standard error پر لکھیں تاکہ وہ capture نہ ہو۔
میرے variable کے آخر میں موجود خالی سطریں کیوں ختم ہو گئیں؟
Command substitution ہر آخری newline کو ہٹا دیتی ہے، صرف آخری newline کو نہیں۔ printf 'hello\n\n\n' > f; v=$(cat f) میں v پانچ bytes رکھتا ہے، جبکہ file میں آٹھ bytes موجود ہیں۔ انہیں برقرار رکھنے کے لیے substitution کے اندر ایک sentinel شامل کریں، پھر v=$(cat f; printf x) کے بعد v=${v%x} استعمال کرکے اسے ہٹا دیں۔ sentinel newlines کے بعد موجود ہوتا ہے، اس لیے bash کے پاس آخر میں ہٹانے کے لیے کچھ نہیں رہتا۔
local out=$(cmd) ہمیشہ کامیابی کیوں ظاہر کرتا ہے؟
local اپنی جگہ ایک الگ command ہے، اور اس line کے بعد $? یہ بتاتا ہے کہ variable declare کرنے میں local کامیاب ہوا یا نہیں۔ substitution کا exit status استعمال ہو کر ضائع ہو جاتا ہے، جس کا مطلب یہ بھی ہے کہ set -e script کو نہیں روکے گا۔ declare، export، typeset اور readonly بھی اسی طرح کام کرتے ہیں۔ local out کو ایک line پر اور out=$(cmd) کو اگلی line پر لکھیں، پھر $? اصل status ظاہر کرے گا۔