Bash command substitution: $() மற்றும் backticks வேறுபாடு
Bash-ல் $() மற்றும் backticks பயன்பாட்டின் முக்கிய வேறுபாடுகளை அறியுங்கள். Subshell-ல் cd மற்றும் மாறிகள் ஏன் செயல்படுவதில்லை மற்றும் nested command சிக்கல்களைத் தவிர்க்கும் முறைகள்.
Bash command substitution என்ன செய்கிறது
Bash command substitution என்பது $(command)-ஐ, அந்த command standard output-க்கு அச்சிட்ட உரையைக் கொண்டு மாற்றுகிறது. Backticks-ஐப் பயன்படுத்தும் பழைய முறையும் இதே வேலையைத்தான் செய்கிறது. பயனர்களுக்கு வியப்பைத் தரும் அனைத்து விஷயங்களும் இரண்டு உண்மைகளிலிருந்து வருகின்றன: அந்த command subshell எனப்படும் தனி process-ல் இயங்குகிறது, மேலும் அதன் 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இதுதான் இந்த அம்சத்தின் முழுமையான செயல்பாடு. wc -l என்பது 3-ஐ அச்சிட்டு, அதைத் தொடர்ந்து ஒரு newline-ஐயும் அச்சிட்டது; அந்த newline நீக்கப்பட்டு, count நீங்கள் விரும்பிய இரண்டு எழுத்துக்களைக் கொண்டுள்ளது. wc -l < three.txt ஒரு வெறும் எண்ணை மட்டுமே அச்சிடுகிறது என்பதைக் கவனிக்கவும், ஏனெனில் standard input-ஐ வாசிக்கும் GNU wc-க்கு அச்சிடுவதற்கு கோப்புப் பெயர் (filename) இல்லை. அதற்குப் பதிலாக wc -l three.txt என்று எழுதினால், நீங்கள் 3 three.txt-ஐப் பெறுவீர்கள்; இது ஒரு மாறுபட்ட string ஆகும், மேலும் இது பிற்காலத்தில் கணக்கீடுகள் (arithmetic) தோல்வியடைவதற்கு ஒரு பொதுவான காரணமாக அமைகிறது.
இந்த வழிகாட்டியின் மீதமுள்ள பகுதி யாரும் எதிர்பாராத நடத்தைகளைப் பற்றியது, ஏனெனில் subshell என்பது ஒரு தனி process ஆகும், மேலும் ஒரு தனி process நீங்கள் தட்டச்சு செய்யும் shell-ஐ மாற்ற முடியாது.
$() பயன்படுத்துங்கள், backticks பயன்படுத்துவதை நிறுத்துங்கள்
இரண்டு வடிவங்களும் செல்லுபடியாகும். $() என்பது POSIX தரநிலையில் உள்ளது, எனவே dash, ash மற்றும் busybox sh ஆகிய அனைத்தும் இதை ஆதரிக்கின்றன. backticks பயன்படுத்துவதற்கு எந்தவொரு போர்ட்டபிலிட்டி (portability) காரணமும் இல்லை, ஆனால் அதைப் பயன்படுத்தக்கூடாது என்பதற்கு இரண்டு உறுதியான காரணங்கள் உள்ளன.
Backticks-ஐ ஒன்றிற்குள் ஒன்றாக (nest) பயன்படுத்த முடியாது
echo "$(echo "$(echo hi)")"
echo "`echo `echo hi``"hi
echo hiஇரண்டாவது வரி echo hi என்ற வார்த்தைகளை அப்படியே அச்சிட்டது. ஷெல் (shell) அடுத்ததாக வரும் backtick-ஐத் தேடுகிறது, எனவே நீங்கள் உள்ளிட்ட இரண்டாவது backtick முதல் backtick-ஐ மூடிவிட்டது. உண்மையில் இயக்கப்பட்ட கட்டளை echo ஆகும், இதற்கு எந்த வாதங்களும் (arguments) இல்லை. இது ஒரு காலியான வரியை அச்சிட்டது, அதன் newline நீக்கப்பட்டது. echo hi என்ற வார்த்தைகள் சாதாரண உரையாக விடப்பட்டன, மேலும் இறுதி backtick ஜோடி ஒரு காலியான கட்டளையை இயக்கியது.
Backticks-ஐ ஒன்றிற்குள் ஒன்றாகப் பயன்படுத்த, ஒவ்வொரு உட்புற backtick-ஐயும் escape செய்ய வேண்டும்:
echo "`echo \`echo hi\``"hiஒவ்வொரு கூடுதல் நிலையும் escape செய்வதை இருமடங்காக்குகிறது. $()-க்கு இது எதவும் தேவையில்லை, ஏனெனில் பார்சர் (parser) ஒரு டெலிமீட்டர் (delimiter) எழுத்தைத் தேடுவதற்குப் பதிலாக அடைப்புக்குறிகளை (parentheses) பொருத்துகிறது.
கட்டளை இயங்குவதற்கு முன்பே Backticks உங்கள் backslashes-ஐ மாற்றுகிறது
echo "$(echo 'a\\b')"
echo "`echo 'a\\b'`"a\\b
a\bஒரே உட்புற கட்டளை வெவ்வேறு வெளியீடுகளை உருவாக்கியது. Backticks-க்குள், உட்புற உரை பகுப்பாய்வு செய்யப்படுவதற்கு முன்பே ஷெல் ஒரு அடுக்கு backslash-ஐ நீக்குகிறது, எனவே ஒற்றை மேற்கோள்கள் (single quotes) எதையும் பாதுகாக்கவில்லை. $()-க்குள், அடைப்புக்குறிகளுக்கு இடையே உள்ள உரை ஒரு சாதாரண ஸ்கிரிப்டாக பகுப்பாய்வு செய்யப்படுகிறது, எனவே ஒற்றை மேற்கோள்கள் நீங்கள் எதிர்பார்ப்பது போலவே செயல்படுகின்றன. இது sed மற்றும் awk ஒன்-லைனர்களில் (one-liners) பெரும் சிக்கலை ஏற்படுத்துகிறது, அங்கு ஒரு விடுபட்ட backslash ஒரு வேலை செய்யும் பேட்டர்னை (pattern) அமைதியாக மாற்றுகிறது.
$()-க்குள் மேற்கோள் காட்டுதல் (quoting) புதியதாகத் தொடங்குகிறது, அதாவது நீங்கள் இரட்டை மேற்கோள்களை (double quotes) escape செய்யாமலேயே ஒன்றிற்குள் ஒன்றாகப் பயன்படுத்தலாம்:
path=/etc/nginx/nginx.conf
echo "$(dirname "$path")"/etc/nginxBacktick சமமான கட்டளைக்கு $path-ஐச் சுற்றி \" தேவைப்படுகிறது. ஒவ்வொரு escape-ம் ஒரு தவறு நடப்பதற்கான வாய்ப்பாகும்.
$() உள்ளே cd கட்டளையிட்டால் ஏன் உங்கள் shell மாறாது
ஏனெனில் $(...) ஒரு புதிய process-ஐ உருவாக்குகிறது. இந்த subshell உங்கள் variables மற்றும் working directory-ன் நகலைப் பெறுகிறது. அது தனது நகலில் மாற்றங்களைச் செய்து, வெளியீட்டை அச்சிட்டு, வெளியேறுகிறது. அந்த நகல் அதனுடன் அழிந்துவிடுகிறது.
cd /tmp/subst-demo
pwd
target=$(cd /etc && pwd)
echo "$target"
pwd/tmp/subst-demo
/etc
/tmp/subst-demoஎந்தப் பிழையும் ஏற்படவில்லை. cd சரியாகச் செயல்பட்டது, மேலும் subshell-க்குள் இருந்த pwd உண்மையாகவே /etc-ஐ அச்சிட்டது. அந்த மாற்றம் மீண்டும் உங்கள் shell-க்கு வர எந்த வழியும் இல்லை, ஏனெனில் ஒரு 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மக்கள் அடிக்கடி சந்திக்கும் இந்த பிழையின் மற்றொரு வடிவத்திற்கும் இதே விதிதான் காரணம்; இது substitution-க்கு பதிலாக pipeline-ஐப் பயன்படுத்துகிறது:
n=0
cat three.txt | while read -r line; do n=$((n+1)); done
echo "$n"0Pipeline-ன் ஒவ்வொரு நிலையும் அதன் சொந்த subshell-ல் இயங்குகிறது, எனவே while loop, n-ன் நகலை மட்டுமே அதிகரித்துவிட்டு வெளியேறியது. Pipe-க்கு பதிலாக redirect-ஐப் பயன்படுத்தினால், அந்த loop உங்கள் shell-லேயே இயங்கும்:
n=0
while read -r line; do n=$((n+1)); done < three.txt
echo "$n"3Bash, pipeline-ன் கடைசி நிலையை தற்போதைய shell-லேயே shopt -s lastpipe மூலம் இயக்க முடியும், ஆனால் job control முடக்கப்பட்டிருக்கும்போது மட்டுமே இது சாத்தியம். Interactive shell-ல் இது எப்போதும் சாத்தியமில்லை. எனவே redirect-ஐப் பயன்படுத்தவும்.
$() உள்ளே இருக்கும் interactive கட்டளைகளின் prompt எங்கே போனது?
ஒரு command substitution, standard output-ஐ ஒரு pipe-க்கு திருப்பிவிடும், ஆனால் standard input-ஐ மாற்றாது. ஒரு நிரல் தனது கேள்வியை standard output-ல் அச்சிட்டுவிட்டு, பதிலுக்காகக் காத்திருந்தால், அந்த கேள்வி பயனருக்குத் தெரியாது, ஆனால் நிரல் காத்திருக்கும். இதனால் terminal உறைந்து போனது போலத் தோன்றும்.
ask() { printf 'Username: '; read -r u; printf '%s\n' "$u"; }
askUsername: deploy
deployமுதல் வரியில் உள்ள deploy என்பது நீங்கள் தட்டச்சு செய்தது. இப்போது அதே function-ஐ ஒரு substitution-க்குள் இயக்கவும்:
v=$(ask)deployநீங்கள் தட்டச்சு செய்யும் எழுத்துக்கள் மட்டுமே தெரியும், இவை நிரலால் அச்சிடப்படாமல், terminal driver-ஆல் எதிரொலிக்கப்படுகின்றன. Prompt வேறெங்கோ சென்றுவிட்டது:
echo "$v"Username: deployஇப்போது prompt அந்த variable-க்குள், பதிலுடன் இணைந்துவிட்டது. ஏனெனில் $() என்பது function standard output-ல் எழுதிய அனைத்தையும் பிடித்துக்கொண்டது. standard input தொடப்படாததால், உங்கள் தட்டச்சு read-ஐ அடைந்தது. "எனது script உறைந்து விடுகிறது, எதையும் அச்சிடவில்லை" என்று வரும் bug report-களின் அடையாளம் இதுதான்.
சில கருவிகள் prompt-களை standard error-க்கு அல்லது நேரடியாக /dev/tty-க்கு எழுதும், அப்போதுதான் அவை அழியாமல் இருக்கும். பல கருவிகள் அவ்வாறு செய்வதில்லை. ஒரு function-ஐ substitution-க்குள் வைத்திருக்க வேண்டியிருந்தால், அதன் prompt-ஐ நீங்களே standard error-க்கு அனுப்பவும்:
ask() { printf 'Username: ' >&2; read -r u; printf '%s\n' "$u"; }
v=$(ask)
echo "$v"Username: deploy
deployStandard error பிடிக்கப்படாது, எனவே prompt உங்கள் terminal-ஐ அடையும், பதில் மட்டுமே v-க்குள் சேரும்.
$() உள்ளே ls ஏன் வித்தியாசமாகத் தெரிகிறது?
ஏனெனில் ls ஆனது file descriptor 1-ல் isatty-ஐ அழைக்கிறது, மேலும் அந்தப் பதிலைப் பொறுத்து அதன் வெளியீட்டு வடிவத்தை மாற்றுகிறது. கட்டளை வரியில் (prompt) அந்த descriptor உங்கள் terminal-ஆக இருப்பதால், ls கோப்புப் பெயர்களை வரிசையாகக் columnas-ல் பரப்புகிறது. substitution-க்குள் அது ஒரு pipe என்பதால், ls ஒரு வரிக்கு ஒரு பெயர் என்ற முறையில் மாற்றிக்கொள்கிறது.
mkdir -p /tmp/tty-demo
cd /tmp/tty-demo
touch alpha beta delta gamma
echo "$(ls)"alpha
beta
delta
gammaஇதே சோதனையானது grep --color=auto-ல் வண்ணங்களையும் (colour), git-ல் pager-ஐயும் முடக்குகிறது. இது ஒரு வசதியாகும். இதன் பொருள், ஒரு script-ஆனது தானாகவே நிலையான, கணினி வாசிக்கக்கூடிய (machine-readable) வெளியீட்டைப் பெறுகிறது; இதற்காக நீங்கள் தனியாகக் கேட்க வேண்டியதில்லை.
இது pipelines குறித்து மக்கள் கேட்கும் ஒரு கேள்விக்கும் பதிலளிக்கிறது. கட்டளை வரியில் ls | sort மற்றும் $(ls | sort) ஆகிய இரண்டும் ஒரே வெளியீட்டைத் தருகின்றன, ஏனெனில் ls-ன் வெளியீட்டில் இரண்டு சந்தர்ப்பங்களிலும் ஒரு pipe இருந்தது. substitution-க்குள் மாறுவது pipeline-ன் கடைசி நிலை மட்டுமே. sort ஒருபோதும் terminal-ஐச் சரிபார்ப்பதில்லை, எனவே அதன் வெளியீடு மாறுவதில்லை. ஒரு pipeline-ன் இறுதியில் terminal-ஐ உணரும் (terminal-aware) கட்டளையை வைத்தால் அது மாறும்; இதனால்தான் நீங்கள் நேரடியாகச் சோதித்த ஒரு pipeline, அதை $()-க்குள் வைக்கும்போது வித்தியாசமாகச் செயல்படுகிறது.
இதிலிருந்து ஒரு எச்சரிக்கை: ls-ன் வெளியீடு வசதியாகத் தெரிந்தாலும், அதை ஒரு script-ல் parse செய்யாதீர்கள். கோப்புப் பெயர்களில் இடைவெளிகளும் (spaces) புதிய வரிகளும் (newlines) இருக்கலாம். அதற்குப் பதிலாக glob-ஐப் பயன்படுத்துங்கள், அல்லது read -d '' உடன் find -print0-ஐப் பயன்படுத்துங்கள்.
அமைதியாக மறையும் trailing newlines
Command substitution, வெளியீட்டின் இறுதியில் உள்ள அனைத்து புதிய வரிகளையும் (newlines) நீக்கிவிடும். கடைசி ஒன்றை மட்டும் அல்ல, அனைத்தையும் நீக்கும்.
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 அளவு கொண்டது. இந்த variable-ல் hello மட்டுமே உள்ளது, எனவே இது 5 bytes அளவு கொண்டது. மூன்று bytes எந்த எச்சரிக்கையும் இன்றி மறைந்துவிட்டன.
இந்த நீக்கம் திட்டமிட்டே செய்யப்படுகிறது, இது பொதுவாக பயனுள்ளது. இதனால்தான் stamp=$(date -u +%Y%m%dT%H%M%SZ), ஒரு வரியை உடைக்காமல் பயன்படுத்தக்கூடிய filename fragment-ஐ உருவாக்குகிறது. இதனாலேயே இந்த 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 மாறுபடலாம். கோப்பின் பெயர் ஒரே வரியில் இருப்பதுதான் முக்கியம்.
இதன் குறைபாடு என்னவென்றால், ஒரு கோப்பின் துல்லியமான bytes-ஐ நகர்த்த உங்களால் $()-ஐப் பயன்படுத்த முடியாது. உங்களுக்கு trailing newlines தேவைப்பட்டால், substitution-க்குள் ஒரு sentinel character-ஐச் சேர்த்துவிட்டு, பிறகு அதை நீக்கிவிடலாம்:
v=$(cat blanks.txt; printf x)
v=${v%x}
printf '%s' "$v" | wc -c8x புதிய வரிகளுக்குப் பின் அமர்வதால், நீக்குவதற்கு trailing newlines எதுவும் இருக்காது. அதன் பிறகு ${v%x} அந்த sentinel-ஐ நீக்கிவிட்டு, அசல் bytes-ஐ அப்படியே விட்டுவிடும்.
தொடர்புடைய இரண்டு விவரங்கள். ஒரு முழு கோப்பை வாசிப்பதற்கு, v=$(<blanks.txt), cat-ஐ இயக்காமலேயே அதே வேலையைச் செய்கிறது, ஏனெனில் bash கோப்பைத் தானே திறக்கிறது. இதுவும் trailing newlines-ஐ அதேபோலவே நீக்கும். மேலும், here-strings எதிர் திசையில் செயல்பட்டு, நீங்கள் குறிப்பிடாத ஒரு புதிய வரியைச் சேர்க்கும்:
wc -c <<< 'abc'4முடிவை மேற்கோள் குறிகளுக்குள் (quote) இடவும், இல்லையெனில் bash அதை பிரித்து glob செய்யும்
மேற்கோள் காட்டப்படாத substitution, word splitting மற்றும் pathname expansion ஆகியவற்றுக்கு உட்படுத்தப்படும். மேற்கோள் காட்டப்பட்டால், இவை இரண்டுமே நடக்காது.
printf 'a b\tc\nd\n' > words.txt
echo $(cat words.txt)
echo "$(cat words.txt)"a b c d
a b c
dமேற்கோள் காட்டப்படாதபோது, IFS-ல் உள்ள எழுத்துகளை வைத்து bash வெளியீட்டைப் பிரிக்கும்; இயல்பாக இவை space, tab மற்றும் newline ஆகும். பின்னர் echo அந்த நான்கு துண்டுகளையும் ஒற்றை space மூலம் இணைக்கும். மேற்கோள் காட்டப்பட்டால், 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-ல் word splitting அல்லது globbing நடக்காது என்பதால், அந்த assignment பாதுகாப்பானது. பாதிப்பு echo $p-ல் தான் ஏற்படுகிறது; அங்கு * தற்போதைய கோப்பகத்தில் (current directory) விரிவாக்கப்படுகிறது (expand). ஒரு config கோப்பிலிருந்து pattern-ஐப் படிக்கும் script, மேற்கோள் குறிகளைத் தவிர்க்கும்போது, அது காணும் அனைத்து கோப்புகளிலும் தேவையற்ற செயல்களைச் செய்யும். ஒவ்வொரு expansion-ஐயும் மேற்கோள் குறிகளுக்குள் இடுங்கள், இந்த வகை பிழைகள் முழுமையாக நீங்கிவிடும். உங்களுக்குத் தேவைப்படும்போது மட்டுமே மேற்கோள் குறிகளைத் தவிர்க்கவும், ஆனால் அது அரிதானது.
local x=$(cmd) எப்போதும் ஏன் 0-ஐத் திருப்பி அளிக்கிறது?
ஏனெனில் local என்பது ஒரு கட்டளை (command), மேலும் $? என்பது அதற்குள் இருந்த substitution-ன் நிலையை அல்ல, மாறாக local-ன் நிலையைத்தான் தெரிவிக்கிறது.
check_bad() { local out=$(false); echo "status: $?"; }
check_badlocal x=$(false)
echo $?
status: 0false 1-ஐத் திருப்பி அளித்து வெளியேறியது, local மாறியை (variable) அறிவிப்பதில் வெற்றி பெற்றது, எனவே அந்த 1 நிராகரிக்கப்பட்டது. declare, export, typeset மற்றும் readonly ஆகிய அனைத்தும் இதே விதமாகவே செயல்படுகின்றன. set -e-ம் இதைத் தடுக்காது, ஏனெனில் shell-ன் பார்வையில் எந்தத் தவறும் நடக்கவில்லை.
அறிவிப்பையும் (declaration) மதிப்பளிப்பையும் (assignment) தனித்தனியாகப் பிரிக்கவும்:
check_good() { local out; out=$(false); echo "status: $?"; }
check_goodlocal x
x=$(false)
status: 1மேல் மட்டத்தில் (top level) செய்யப்படும் ஒரு சாதாரண மதிப்பளிப்பு, அதன் கடைசி command substitution-ன் நிலையை ஏற்கனவே தெரிவித்துவிடும்:
out=$(exit 3)
echo $?x=$(false)
echo $?
3இது systemd service மற்றும் timer-ன் கீழ் இயங்கும் health check-களில் மிக முக்கியமானது. ஏனெனில், மறைக்கப்பட்ட exit code-ஆல், அந்தப் பணி நடக்காமலேயே, ஒவ்வொரு முறையும் unit வெற்றி பெற்றதாகவே காட்டும்.
உங்களுக்கு உண்மையில் தேவைப்படும் in-shell மாற்றுகள்
பெரும்பாலானோர் $(...)-ஐப் பயன்படுத்துகிறார்கள், ஏனெனில் அவர்களுக்குத் தரவு ஒரு variable-ல் தேவைப்படுகிறது. ஆனால் பெரும்பாலும் அவர்களுக்குத் தேவைப்படுவது capture அல்ல, input மட்டுமே. இந்த நான்கு முறைகளும் உங்கள் தற்போதைய 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-ல் அமைக்கப்படும் எதுவும் 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-ன் அம்சம். எனவே, #!/bin/sh கொண்ட script-ஐ Ubuntu அல்லது Debian-ல் இயக்கினால், அது 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-லேயே இயங்குவதால், இரண்டு variable-களும் நீங்கள் பயன்படுத்தக்கூடிய இடத்திலேயே அமையும். read -r a b <<< "$(some-command)" என்பது ஒரு வரி output-லிருந்து இரண்டு fields-ஐப் பிரித்தெடுக்கப் பயன்படும் சாதாரண முறையாகும்.
முழு கோப்புகளுக்கும் mapfile
mapfile -t lines < three.txt
echo "${#lines[@]}"
echo "${lines[1]}"3
betamapfile (இதனை readarray என்றும் அழைக்கலாம்) ஒரு கோப்பைத் தற்போதைய shell-ல் உள்ள array-க்குள் வாசிக்கும். -t ஒவ்வொரு element-ன் இறுதியிலும் உள்ள newline-ஐ நீக்கும். இதற்கு bash 4 அல்லது அதற்குப் பிந்தைய பதிப்பு தேவை. Ubuntu 24.04-ல் bash 5.2 இருப்பதால், தற்போதைய அனைத்து server image-களிலும் இது கிடைக்கும்.
ஸ்கிரிப்டை commit செய்வதற்கு முந்தைய சரிபார்ப்புப் பட்டியல்
$(command)-ஐ எழுதவும், மேலும் நீங்கள் குறிப்பாகப் பிரித்தெடுக்க விரும்பினால் ஒழிய அதை"$(command)"என மேற்கோள் காட்டவும்.- trailing newlines நீக்கப்பட்டுவிட்டதாகக் கருதவும். உங்களுக்கு அவை மீண்டும் தேவைப்பட்டால், ஒரு sentinel character-ஐச் சேர்க்கவும்.
- substitutions-ல் interactive commands-ஐத் தவிர்க்கவும், அல்லது அவற்றின் prompts-ஐ standard error-க்கு அனுப்பவும்.
out=$(command)-ன் exit status முக்கியமானது எனில்,local out-ஐத் தனி வரியில் எழுதவும்.- input-லிருந்து variables-ஐ அமைக்க, pipe-க்கு பதிலாக redirect அல்லது process substitution-ஐப் பயன்படுத்தவும்.
மக்கள் புதிய VPS-ஐ அமைக்கும்போது எழுதும் முதல் சிறிய ஸ்கிரிப்டுகளில் இந்த வடிவங்கள் வெளிப்படுகின்றன, மேலும் தோல்விகள் அமைதியாகவே இருக்கும். ஒரு கோப்பின் பெயருக்குள் prompt-ஐப் பதிவு செய்யும் backup ஸ்கிரிப்ட் அல்லது exit code-ஐ மறைக்கும் health check போன்றவை, தொடர்ந்து வெற்றியை மட்டுமே காட்டும். நீங்கள் ஒரே ஸ்கிரிப்டை பல server-களில் இயக்கும்போது இதன் பாதிப்பு அதிகரிக்கிறது, ஏனெனில் நீங்கள் படிக்காத output, இப்போது இருபது இயந்திரங்களில் நீங்கள் படிக்காத output-ஆக மாறுகிறது.
FAQ
$() உள்ளே cd கட்டளையை இயக்கும்போது எனது தற்போதைய directory ஏன் மாறுவதில்லை?
$(...) அதன் கட்டளையை ஒரு subshell-ல் இயக்குகிறது. இது உங்கள் working directory மற்றும் variables-ன் நகலை வைத்திருக்கும் ஒரு தனி process ஆகும். cd அந்த நகலை மட்டுமே மாற்றுகிறது; process முடிந்ததும் அந்த நகல் நீக்கப்பட்டுவிடும். ஒரு subshell அதன் standard output மற்றும் exit status-ஐ மட்டுமே திருப்பி அனுப்ப முடியும் என்பதால், directory மாற்றத்தை parent shell-க்கு கொண்டு செல்லும் வசதி இல்லை. உங்களுக்கு அந்த directory-ன் பாதை தேவைப்பட்டால், அதை target=$(cd /etc && pwd) மூலம் capture செய்து, பின் "$target"-ஐப் பயன்படுத்தவும். உங்கள் shell-ஐ அந்த directory-க்கு மாற்ற விரும்பினால், substitution இல்லாமல் cd-ஐ நேரடியாக இயக்கவும்.
bash-ல் $() மற்றும் backticks-க்கு என்ன வித்தியாசம்?
எளிமையான கட்டளைகளுக்கு இவை இரண்டும் ஒரே முடிவையே தருகின்றன, ஆனால் முக்கியமான இரண்டு வேறுபாடுகள் உள்ளன. $() நேரடியாக nesting செய்ய அனுமதிக்கிறது, ஏனெனில் parser அடைப்புக்குறிகளை (parentheses) சரியாக அடையாளம் காணும்; ஆனால் backticks-க்கு ஒவ்வொரு nesting நிலைக்கும் ஒரு escaped backtick தேவைப்படும். மேலும், inner command-ஐ parse செய்வதற்கு முன்பே backticks ஒரு அடுக்கு 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 சரியாகச் செயல்படாது, எனவே backticks-ஐப் பயன்படுத்துவதற்கு எந்தவிதமான portability காரணமும் இல்லை.
ஒரு கட்டளை கேள்வி கேட்கும்போது எனது script ஏன் prompt காட்டாமல் hang ஆகிறது?
Command substitution, standard output-ஐ ஒரு pipe-க்கு திருப்பிவிடும், ஆனால் standard input-ஐ உங்கள் terminal-டனேயே வைத்திருக்கும். ஒரு program அதன் prompt-ஐ standard output-க்கு அனுப்பினால், அந்த prompt variable-க்குள் capture ஆகிவிடும்; ஆனால் அதன் பின்னால் உள்ள read உங்கள் உள்ளீட்டிற்காகக் காத்திருக்கும். Terminal-ல் நீங்கள் தட்டச்சு செய்யும் எழுத்துக்கள் மட்டுமே தெரியும். கேள்வியை substitution-க்கு வெளியே நகர்த்தவும், அல்லது prompt-ஐ printf 'Username: ' >&2 மூலம் standard error-க்கு அனுப்பவும், அப்போதுதான் அது capture ஆகாது.
எனது variable-ன் இறுதியில் இருந்த காலி வரிகள் (blank lines) ஏன் மறைந்துவிட்டன?
Command substitution, இறுதியில் உள்ள அனைத்து புதிய வரிகளையும் (newlines) நீக்கிவிடும். printf 'hello\n\n\n' > f; v=$(cat f)-ன் விளைவாக, கோப்பில் எட்டு bytes இருந்தாலும் v ஐந்து bytes-ஐ மட்டுமே கொண்டிருக்கும். அவற்றை அப்படியே வைத்திருக்க, substitution-க்குள் ஒரு sentinel-ஐச் சேர்த்து, பின் v=$(cat f; printf x) மற்றும் v=${v%x} மூலம் அதை நீக்கவும். Sentinel புதிய வரிகளுக்குப் பின்னால் இருப்பதால், இறுதியில் நீக்குவதற்கு bash-க்கு எதுவும் இருக்காது.
local out=$(cmd) ஏன் எப்போதும் வெற்றியை (success) காட்டுகிறது?
local என்பது ஒரு தனி கட்டளை, எனவே அந்த வரியைத் தொடர்ந்து வரும் $?, local variable-ஐ உருவாக்கியதா என்பதை மட்டுமே குறிக்கும். Substitution-ன் exit status பயன்படுத்தப்பட்டு நீக்கப்பட்டுவிடும், அதாவது set -e அந்த script-ஐ நிறுத்தாது. declare, export, typeset மற்றும் readonly ஆகியவையும் இதேபோல்தான் செயல்படுகின்றன. local out-ஐ ஒரு வரியிலும், out=$(cmd)-ஐ அடுத்த வரியிலும் எழுதினால், $? உண்மையான status-ஐக் காட்டும்.