Portál AbcLinuxu, 26. října 2025 23:49
Občas když lidé píší shellové skripty, používají obraty z Bashe, aniž by si to uvědomovali. Někdy je pro změnu používají vědomě tam, kde je to zbytečné. V tomto článku si ukážeme několik příkazů z Bashe, jimž je třeba se vyhnout, chceme-li kompatibilitu, například s Dash.
Má-li skript fungující v Bashi hashbang "#!/bin/sh", mělo by to znamenat, že jej lze spustit i pomocí Bourne shellu, dash a ksh. Pokud tomu tak není, je vhodné jej změnit na "#!/bin/bash" nebo jej přepsat. Jak se rozhodnout?
Jak už zde zaznělo, silným argumentem pro používání standardní syntaxe Bourne shellu (a kompatibilních shellů, jako je dash či ksh) je zpětná kompatibilita, která ovšem souvisí i s externími nástroji, které ze skriptu voláte. Dalším kladem je (v některých případech) rychlost vykonávání, která je zpravidla u jednodušších shellů vyšší. Jiný argument pro je ten, že menší shell je vhodnější pro nasazení na mobilní či embedded zařízení s omezenými zdroji (málo paměti, pomalý procesor).
Pokud pro vás ovšem zmíněné vlastnosti nejsou důležité, pak asi neexistuje pádný důvod se nějak omezovat. Například skript pro převod videa zaprvé nebude mít pravděpodobně tak dlouhou životnost, aby bylo nutné si nějak zvlášť lámat hlavu s kompatibilitou a co se týče embedded zařízení, tak pro ty takový skript z principu není určený. Pokud vás zaujala zpětná kompatibilita, je třeba si uvědomit, že ta souvisí i s používáním kompatibilních externích nástrojů – budete-li například v shellovém skriptu používat rozšíření nástrojů GNU (oproti standardu), tak to poněkud postrádá smysl. V každém případě je třeba nejprve se rozhodnout, zda přepis nebude jen ztrátou času.
Lidé v Bashi často nahrazují příkaz test voláním [[, které je (dle mého zkoumání) z nějakého důvodu rychlejší než [. Toto je ovšem nekompatibilní. Zrovna tak není možné používat "(( ))" nebo "$[ ]". Ovšem "$(( ))" funguje, i když oproti Bashi omezeně. Ukázka v Bashi:
#!/bin/bash x=0 ((x++)) x=$[x+1] if((x==2)); then echo "V pořádku..." fi file="/tmp/testfile" [[ ! -e "$file" ]] && echo "$file neexistuje"
Kdyby tento skript dával smysl a vyplatilo se nám ho přepisovat, přepis by vypadal takto:
#!/bin/sh x=0 : $(( x+=2 )) if [ "$x" -eq 2 ]; then echo "V pořádku..." fi file="/tmp/testfile" [ ! -e "$file" ] && echo "$file neexistuje"
Test "[" je v klasickém shellu omezený oproti Bashi, například pro porovnání dvou řetězců nepoužívá operátor "==", ale "=". Někdy se v Bashi můžete setkat s přesměrováním "<<<", které přesměruje zadaný řetězec na standardní vstup programu. Na takové vychytávky zapomeňte, místo toho je třeba použít například echo či printf a příkaz dát za rouru:
# bash [ "$1" == "$2" ] && tr -d 'x' <<< "xxaxxhxxoxxjx" # sh [ "$1" = "$2" ] && echo "xxaxxhxxoxxjx" | tr -d 'x'
Podstatná věc, na kterou v sh můžete zapomenout, jsou pole. Je to jedna z věcí, která může zkomplikovat přepis skriptu. Bash má také více vestavěných funkcí a klíčových slov: v sh neexistuje declare, disown, shopt, time, select a function či let. Některé vestavěné funkce, které má sh společné s Bashem, jsou ochuzené o různé přepínače či funcionalitu, například echo nemá přepínač -e a read vyžaduje název proměnné.
Zrovna tak není možné přesměrovat standardní vstup i výstup najednou pomocí &> soubor, ale je třeba použít > soubor 2>&1.
### Bash
function f {
echo -e "\n\nTest" >&2
read
echo ${REPLY:-nic...}
}
f &> out
### sh
f() {
printf "\n\nTest\n" >&2
read REPLY
echo ${REPLY:-nic...}
}
f > out 2>&1
Pochopitelně nefungují ani věci jako PATH+=":~/bin" nebo ${!BASH*}. Místo {1..10} je potřeba použít $(seq 1 10) a $UID je nutné nahradit za $(id -ru). V sh také není source, místo toho lze použít jednoduše . (tečku).
### Bash source /etc/profile PATH+="~/bin" uid="$UID" ### sh . /etc/profile PATH="$PATH:$HOME/bin" # sh neprovádí expanzi "~" v PATH uid="$(id -ru)"
Tolik tedy k základním věcem, na které je třeba dát si pozor. Na anglické Ubuntu wiki se tímto tématem zabývají také, viz Dash as /bin/sh. Pochopitelně, pokud chcete mít jistotu, že je skript kompatibilní se sh, tak není nic jednoduššího, než jej spustit např. pomocí dash.
checkbashismsVývojáři Debianu v polovině roku 2010 odstraňovali použití Bashe ze sestavovacích skriptů a při té příležitosti napsali v Perlu program checkbashisms, který se snaží detekovat některé "bashismy", což je označení pro nekompatibilní rozšíření Bashe. V některých případech rovnou vypisuje i náznak triviálního řešení.
possible bashism in /tmp/ncoda line 235 (alternative test command ([[ foo ]] should be [ foo ])):
if [[ "$oformat" == "wav" ]]; then
possible bashism in /tmp/ncoda line 235 (should be 'b = a'):
if [[ "$oformat" == "wav" ]]; then
possible bashism in /tmp/ncoda line 253 (<<< here string):
oformat="$(tr '[[:upper:]]' '[[:lower:]]' <<< "$oformat")"
possible bashism in /tmp/ncoda line 368 (declare):
declare option_${OPTION}=true
possible bashism in /tmp/ncoda line 385 ('((' should be '$(('):
if (( $# == 0 )); then
V tomto výpisu je pět řádků, které by bylo třeba upravit, aby bylo korektní použít hashbang #!/bin/sh. Každý z těchto konkrétních případů jsem popsal v článku.
((x++));
do POSIXu, je toto:
: $(( x+=1 ));
což má výhodu, že jméno proměnné píšete jen jednou.
Řetězec na vstup dostanete taky here-docem (výhoda: dá se použít i pro jiné deskriptory než 0):
command <<E1 3<<E2
... tohle jde na fd 0
E1
... a tohle na 3
E2
Ale pokud mam slozitejsi skript, tak zrovna [[ expr ]] urcite nechci nahrazovat, protoze [[ expr ]] je build-in a [ expr ] mi forkne dalsi proces. Proto je taky [[ rychlejsi..
V bashi jsou i test a [ builtin. Smysl [[ ... ]] vidím jen v tom, že má navíc operátor =~ (matchování regulárních výrazů) a že =, == a != matchují podle wildcards. Což ale může být dost matoucí, protože jsem přesvědčen, že nemalá část těch, kdo [[ ... ]] používají, tohle neví a myslí si, že porovnávají na rovnost.
[ expr ] mi forkne dalsi procesOpravdu? Najděte mi tedy ten fork:
jirka@debian:/tmp$ strace sh -c '[ 0 = 0 ]'
execve("/bin/sh", ["sh", "-c", "[ 0 = 0 ]"], [/* 46 vars */]) = 0
brk(0) = 0x805f000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb785d000
access("/etc/ld.so.preload", R_OK) = 0
open("/etc/ld.so.preload", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=35, ...}) = 0
mmap2(NULL, 35, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0xb785c000
close(3) = 0
munmap(0xb785c000, 35) = 0
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=140547, ...}) = 0
mmap2(NULL, 140547, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb783a000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320m\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1323460, ...}) = 0
mmap2(NULL, 1333608, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb76f4000
mmap2(0xb7834000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13f) = 0xb7834000
mmap2(0xb7837000, 10600, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7837000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb76f3000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76f38d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7834000, 8192, PROT_READ) = 0
mprotect(0xb787b000, 4096, PROT_READ) = 0
munmap(0xb783a000, 140547) = 0
getpid() = 20048
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0
geteuid32() = 1000
brk(0) = 0x805f000
brk(0x8080000) = 0x8080000
getppid() = 20047
stat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=16384, ...}) = 0
stat64(".", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=16384, ...}) = 0
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {0x8056520, ~[RTMIN RT_1], 0}, NULL, 8) = 0
rt_sigaction(SIGQUIT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_DFL, ~[RTMIN RT_1], 0}, NULL, 8) = 0
rt_sigaction(SIGTERM, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGTERM, {SIG_DFL, ~[RTMIN RT_1], 0}, NULL, 8) = 0
exit_group(0) = ?
Tohle je dash, ale v bashi je to podobný, jen delší.
a pritom bys mel psat #!/usr/bin/env bashJako aby mi ty skripty nefungovaly při startu systému, pokud mám /usr na samostatném oddílu, jo?
Zrovna tak není možné přesměrovat standardní vstup i výstup...nema tam byt standartni i chybovy vystup?
Nebijte mě, ale boot trvá tak 20 - 30 s včetně bios.Jasně, takhle dlouho trvá i na Arch Linuxu, kterej má initskripty v Bashi. Takže zkrácení o desítky sekund by bylo v podstatě zrušení bootu
x=$((x+1)) urcite neni kompatibilni s Bourne Shell.
$ sh -c 'x=1; x=$((x+1))'
sh: syntax error at line 1: `x=$' unexpected
$ sh -c 'x=1; x=$(($x+1))'
sh: syntax error at line 1: `x=$' unexpected
To je feature az od Korn Shell.
read dostane do podprocesu, těžko si šáhneme na proměnné z jeho výstupu:
read auto <<< $(echo nazdar | tr nr 'M '); echo $auto # Funguje, protože proměnná $auto je ve stejném shellu
echo nazdar | tr nr 'M ' | read auto; echo $auto # Většinou nefunguje, protože proměnná $auto zmizí i se svým podprocesem, viz níže
A někdy vás to opravdu překvapí - jako když ten příkaz read z příkladu výše začne najednou někde fungovat - a objevíte tím pádem nenápadnou větu z manuálové stránky ksh, sekce pipeline:
Each command, except possibly the last, is run as a separate process;A to jsou jen dvě drobnosti namátkou. Navíc autor článku správně zmiňuje fakt, že nemá smysl přepisovat konstrukce ve skriptu, když by člověk zůstal u GNU rozšíření ostatních programů - takže se k tomu přidají další konstrukce obcházející tyto nedostatky - a kód roste a roste... Zkrátka, je to procházka růžovým sadem (z pohledu ostatních - přeci si jen hraješ se skripty, co na tom je? Žádné programování kernelu...), kde v každém růžovém keři je minimálně jeden Semtex (z pohledu těch, co to zkusili). Kdysi jsem měl dvě nabídky práce: buď dělat v céčku a s Oracle databází nebo za o polovinu vyšší plat jít do jedné banky spravovat jejich shellové skripty. Zvolil jsem to první - a když jsem pak pomáhal s pár skripty pro zákazníky, pochopil jsem, jak moc mě v té chvíli osvítilo, že jsem se nenechal penězi zlákat...
function a {
typeset var localvariable = 0;
}
V tomto pripade promenna localvariable neni lokalni a je inicializovana pouze jednou.
Pokud by ale byla fce "a" deklarovana jako "function a()" tak by byla localvariable inicializovana pri kazdem volani.
Aspon dalsi zajimavy clanek tady na abc.
Lidé v Bashi často používají příkaztestvoláním[[, …
Tady asi mělo být spíš nahrazují než používají.
sh") je definováno POSIXem (příslušná kapitola v poslední verzi zde, rozhodně lze doporučit k přečtení). Příliš nechápu, proč je v článku sh volně zaměňován s dashem. Skoro mi až přijde, že v tom sám autor má tak trochu zmatek, slovo POSIX se v článku neobjevuje ani jednou...
echo $(whoami)
echo `whoami`btw, jak se da napsat ` ve windows?
Na druhou stranu tam znak ` nikde nepotřebujete, leda byste byl úchyl používající šmejďárnu typu cygwin.A co pracovat vzdáleně na nějakém unixu?
sh je tento:
$ echo $(whoami)
syntax error: `(' unexpected
$ echo `whoami`
dave
V bashi je ovšem často pohodlnější používat $( ). Je to přehlednější (zvlášť při vnořování), nedá se to snadno zaměnit.
A nakonec rozdíl (v bashi):
When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by $, `, or \. The first backquote not preceded by a backslash terminates the command substitution. When using the $( command ) form, all characters between the parentheses make up the command; none are treated specially.
$ uname -a
SunOS srv 5.6 Generic_105181-39 sun4u sparc SUNW,Ultra-5_10
$ echo $(whoami)
ksh: whoami: not found
$ echo $(who am i)
xxxxxxx pts/1 Feb 17 12:20 (xxxxxxxxxxx)
$ sh
$ echo $(whoami)
syntax error: `(' unexpected
# uname -a SunOS dedek 5.6 Generic_105181-39 sun4u sparc SUNW,Ultra-5_10 # exec /usr/xpg4/bin/sh # echo $(/usr/ucb/whoami) juzrVyřešeno, byť systém už dávno není podporován, léta na něj neexistují patche a jen blázen by nechával v produkci cokoli staršího než Solaris 9. A po celou dobu až do současných verzí je to pořád stejné, na stejném místě a se stejným chováním. A překvapení, i AIX má /usr/ucb a hle, AIXovský /bin/sh je ve skutečnosti normovaný ksh a většina toolů v AIXu je prostě POSIX/XPG4. Pokud chcete přenositelnost, nezbývá než číst dokumentaci (
, užívat POSIX/XPG4 nástroje a upravovat cesty. Shell? ksh. To je normované a konzistentní. awk? nawk. sed, ps atd - opět hledejte POSIXové. Složité konstrukce, výrazy apod? Proč v shellu?
Ve start skriptech systému vážně netřeba speciálních konstrukcí a už vůbec ne přenositelnosti. A startovat aplikaci bude stejně lepší s ksh (nebo perl, etc). Buď totiž startujete obecnou aplikaci a potřebujete přenositelnost, ale tehdy vám stačí jednoduchý sh wrapper kolem skutečného start skriptu, a ten už může být v čemkoli a to cokoli klidně distribuujte s aplikací. Nebo děláte komplikovanou akci při startu systému (nebo v udev na Linuxu a tak), která musí doběhnout i s nenamountovaným /usr apod, ale pak to stejně bude silně system-specific a šijete to tedy na míru daného shellu a systému.
Přeji trpělivost, dobré oči a pěkný den.
Zase někdo, kdo tvrdí, že něco nejdenetvrdim, ze neco nejde. jen jsem reagoval na skutecnost, ze vychozi nastaveni muze nekoho prekvapit, pokud se systemem bezne nepracuje. takze jste se zbytecne rozepisoval, ale treba to nekomu jinemu poskytlo zajimave informace.
[ conditional_expr ] # Provided for Bourne shell compatibility (obsolete) [[ conditional_expr ]] # POSIX and Korn shell specific syntax (preferred)... Tak nevím...
nebo dokonce jejich kombinacíPomocí kombinace se na české píše jenom pípa
|, ale na spoustě klávesnic je „pička key“ (klávesa označovaná v GNOME jako <|>), která píše | a \.
Mně zase přijde praktičtější zvyknout si na jedno rozložení a pak používat to. Ale je pravda, že takových jako já asi moc není.Já taky používám pořád českou QWERTY. Ze známých osobností pak minimálně RH a to už něco znamenánebo dokonce jejich kombinací
A ako som skusal, tak na anglickej je to ta klavesa vedla "1" (niekomu to nefunguje?)...
...se přestal používat ve skriptech na úkor o dost výkonnějšího Dash...Ach jo.
?? Ach ne....se přestal používat ve skriptech na úkor o dost výkonnějšího Dash...Ach jo.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.