Egy tanácsot a kezdő bash-programozók újra és újra hallani fognak:
Tedd idézőjelbe a változóidat!
Miért is?
Amikor kifejtesz egy változót (például $name), szinte mindig a változó értékét szeretnéd egyetlen stringként (vagy számként) használni.
Nem szeretnéd, hogy az érték váratlanul két vagy több külön stringként legyen kezelve.
Az idézőjelek jelentik az elsődleges mechanizmust arra, hogy egy szövegrész egyetlen szóként legyen kezelve. Két fő fajtájuk van.
A kettős idézőjelek lehetővé teszik a paraméterkifejtést, az aritmetikai kifejtést és a parancshelyettesítést.
echo "This is bash version $BASH_VERSION"
Egy literál kettős idézőjelet úgy tehetsz a kettős idézőjelek közé zárt stringbe, hogy egy backslash-t teszel elé.
echo "She said \"Let me teach you about bash.\""
Az egyszeres idézőjelek nem engedélyeznek semmilyen kifejtést.
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
Egy egyszeres idézőjelet nem lehet beágyazni az egyszeres idézőjelek közé zárt stringbe.
Mi is a baj pontosan az echo $name paranccsal?
Amikor a shell feldolgoz egy kódsort, sorrendben többféle kifejtést végez. Az utolsó kifejtések közül kettő a szófelbontás és a fájlnévkifejtés.
A paraméterkifejtés, a parancshelyettesítés vagy az aritmetikai kifejtés minden olyan eredménye, amely nem kettős idézőjelek között jött létre, részt vesz a „szófelbontásban”.
A shell fogja a kifejtett eredményt, és minden olyan karaktersorozatnál szétvágja, amely szerepel a $IFS értékében.
Alapértelmezés szerint ez a szóköz, a tabulátor és az újsor.
Ezt egy shell-ciklussal is láthatjuk működés közben:
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
kimenete:
1 The quick brown fox jumps.
De idézőjel nélkül:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
kimenete:
1 The
2 quick
3 brown
4 fox
5 jumps.
Nézzük meg egy másik $IFS-értékkel:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
kimenete:
first
second
third
A szófelbontás kikapcsolásának egyik technikája, ha a $IFS-t üres stringre állítod
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
kimenete:
1 The quick brown fox jumps.
A változók idézőjel nélkül hagyása azonban nem ajánlott, hacsak nem kapcsolod ki a fájlnévkifejtést is. Hogy ezt hogyan teheted meg, azt alább meglátjuk.
A „glob” minták segítségével tömören kifejezhetsz egy fájlhalmazra illeszkedő mintát.
Például egy könyvtár összes Markdown-fájlját a *.md mintával illesztheted.
A glob-minták a Unix feltalálása óta léteznek.
A glob helyettesítő karakterei:
A * nulla vagy több tetszőleges karakterre illeszkedik,
A ? pontosan egy tetszőleges karakterre illeszkedik,
A [...] pontosan egy karakterre illeszkedik a zárójelek között felsorolt karakterek halmazából.
[abc] pontosan egy olyan karakterre illeszkedik, amely a, b vagy c.
^ után)
[][] egy karakterhalmaz, amely vagy egy csukó, vagy egy nyitó zárójelre illeszkedik.[[\]]
[x-y] pontosan egy olyan karakterre illeszkedik, amely a x és y közötti tartományba esik
^ után), vagy a csukó zárójel előtti utolsó karakternek.
[-xy] és a [xy-] is pontosan egy olyan karakterre illeszkedik, amely -, x vagy y
[x\-y]
[0-9A-Fa-f] egy hexadecimális számjegyre illeszkedik[^abc] pontosan egy olyan karakterre illeszkedik, amely NEM a, b vagy c,[^x-y] pontosan egy olyan karakterre illeszkedik, amely NEM esik a x és y közötti tartományba[[:character_class:]] egy olyan karakterre illeszkedik, amely a megnevezett „character_class”-ba tartozik
lower -- kisbetűkupper -- nagybetűkalpha -- betűkdigit -- számjegyekalnum -- betűk és számjegyekspace -- whitespaceblank -- vízszintes whitespace (szóköz és tabulátor)xdigit -- hexadecimális számjegyekword -- azonosítóban engedélyezett karakterek (betűk, számok, alulvonás)punct -- írásjelekcntrl -- vezérlőkarakterekgraph -- „látható” karakterekprint -- látható karakterek és a szóköz[[:alpha:]] osztály a [[:lower:][:upper:]] osztályokból áll[[:alnum:]] osztály a [[:alpha:][:digit:]] osztályokból áll[[:word:]] osztály a [[:alnum:]_] osztályból áll[[:word:]] és a [[:punct:]] osztálynak[[:graph:]] osztály a [[:alnum:][:punct:]] osztályokból áll[[:print:]] osztály a [[:graph:] ] osztályból áll -- csak a szóközből (oktális 040), semmilyen más whitespace-ből nem.Egy fájllistán végigmehetsz egy for ciklussal
for file in *.csv; do
do_something_with "$file"
done
Figyeld meg, hogy a "$file" változó idézőjelben van.
A glob minta olyan fájlokat is visszaadhat, amelyek nevében szóköz van.
Idézőjelbe kell tennünk a változót, hogy a do_something_with parancs pontosan egy argumentumot kapjon: a fájlnevet.
A glob minták nem reguláris kifejezések.
.
?
.*
*
.* glob minta azt jelenti: „olyan fájlnévre illeszkedik, amelynek első karaktere egy literál pont, amelyet nulla vagy több tetszőleges karakter követ”..+
?*
A Bash kiterjesztett mintákat kínál, amelyek közelebb állnak a reguláris kifejezésekhez.
Vannak helyzetek, amikor el szeretnéd nyomni a fájlnévkifejtést. A felhasználói bemenet kezelése egy ilyen eset. Vegyük ezt a kódrészletet
read -p "Enter something: " user_input
echo $user_input
Ha a felhasználó beír egy csillagot (*), mi lesz a kimenet?
Az aktuális könyvtárban lévő fájlok listája.
Valószínűleg nem ezt szeretnéd.
Egy másik példa, amikor mintákat adunk argumentumként olyan programoknak, amelyek mintákat várnak.
A tr programot gyakran használjuk szövegmanipulációra. Egy változó értékének kisbetűsítéséhez ezt teheted
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
kimenete:
my puppet
Gyakori hiba, hogy elfelejtjük idézőjelbe tenni a tr argumentumait: tr [:upper:] [:lower:]
Miért jelent ez problémát?
Az idézőjel nélküli glob minták arra a fájllistára lesznek kifejtve, amelyek illeszkednek.
Tegyük fel, hogy a szkriptedet használónak vannak p, u és w nevű fájljai az aktuális könyvtárban:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
Ez hibát eredményez!
Az idézőjel nélküli [:upper:] minta a :, u, p, e vagy r bármelyikére illeszkedik.
Az idézőjel nélküli [:lower:] minta a :, l, o, w, e vagy r bármelyikére illeszkedik.
A Bash az első mintához a p és u fájlt párosítja, a második mintához a w fájlt, és a tr így hívódik meg:
echo "$var" | tr p u w
És ez rossz számú argumentum a tr számára.
A set paranccsal kapcsolhatod ki a fájlnévkifejtést.
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
A beépített shopt parancsnak több beállítása is szabályozza, hogyan működik a fájlnévkifejtés.
Néhány érdekes közülük:
shopt -s nocaseglob -- kis- és nagybetűt nem megkülönböztető illesztésshopt -s extglob -- kiterjesztett minták engedélyezéseshopt -s nullglob -- ha egyetlen fájl sem illeszkedik, a mintát semmire cseréli.
Az alapértelmezett viselkedés, hogy a minta a helyén marad literál stringként.