Folklórny príklad: chcete si v shellovom skripte spočítať počet riadkov súboru, čiže urobiť si vlastnú verziu wc -l
.
Ako riešenie vám napadne:
#!/bin/sh
POCET=0
cat $1 | while read
do
POCET=$(($POCET + 1))
done
echo $POCET
Skript sa zavolá cez
./wc.sh /etc/passwd
Lenže výsledkom je…
0
Prečo? Veď /etc/passwd
určite nemá nula riadkov!
Problém je dostatočne popísaný vo FAQ k Bashu. Jadro spočíva v správaní shellu: príkaz cat $1 | while ...
predstavuje rúru (pipeline), ktorá je spúšťaná v podshelli (subshell). Shell spustí rúru v separátnom detskom procese, ktorý zdedí premenné, popisovače súborov, premenné prostredia atď. Lenže zmeny v týchto premenných v rámci podshellu sa po dobehnutí subshellu samozrejme stratia.
V našom príklade sa premenná POCET
mení v rámci subshellu, lenže príkaz echo $POCET
je opäť súčasťou rodičovského shellu. Táto premenná teda nebola nijak ovplyvnená zmenami v subshelli a na výstupe sa teda ocitne korektná hodnota: 0.
Riešenia?
Presmerovanie miesto cat
Jedno z riešení je nahradiť zbytočnú kombináciu cat
/ while read
jednoduchým presmerovaním:
cat $1 | while read
do
...
done < $1
To funguje všade, s výnimkou shellu Bourne shellu (sh
), kde je možné obísť túto vlastnosť trikom s exec
om a deskriptormi súborov.
Zoskupenie príkazov
Prostredie { ... }
slúži na zoskupovanie príkazov (command grouping). Postupnosť príkazov medzi kučeravými zátvorkami sa spustí v aktuálnom shelli, čo možno použiť v kombinácii s presmerovaním na všakovaké triky.
POCET=0
cat $1 | {
while read
do
POCET=$(($POCET + 1))
done
echo $POCET
}
Celá rúra sa samozrejme spustí v podshelli. V tomto prípade sme však zoskupili while
a echo
a teda ešte pred dobehnutím subshellu vidíme správnu hodnotu premennej POCET
. Samozrejme, v rámci rodiča je premenná POCET
nezmenená.