bash: változó leválasztása - variable mangling

Fórumok

Sziasztok,

Gondoltam indítok egy témát - ismerkedem a bash sel. Észrevettem egy jelenséget, ami párszor megnehezítette a dolgomat.Itt van pl ez a sor:

find -name "*.${i}" -type f -exec mv --target-directory=${i} {} \;

Ennél muszáj {} jelek közé tenni az i változót,mert különben nem működik.

De vannak olyan helyzetek, amikor elegendő a () jelek is:

echo "$(i)és itt folytatódik a szöveg"

Utánanéztem a google-m és ezt variable mangling nek nevezik angolul, elvileg. A Büki András Héjprogramozás címe is említi rögtön az elején, a 7. oldalon. Az első esetben a probléma abból adódik szerintem, hogy maga a find program úgy lett leprogramozva, hogy a -name kapcsolót követően "" jelek közé kell tenni, azt amit keresünk, de ha változó az, akkor kellenek a {} jelek köré.
Van még ehhez hasonló nyalánkság ebben a témában, amiről már tudtok, mert beleakadtatok a mindennapok során? Vagy itt vége ennek a történetnek?

Ha valami olyan bődületes és egy hozzáértő számára triviális dolgot hoztam fel, akkor Tőlük elnézést kérek.

Hozzászólások

"De vannak olyan helyzetek, amikor elegendő a () jelek is:

echo "$(i)és itt folytatódik a szöveg""

A $() az a command substitution egyik formája, ezért a shell azt az "i"-t nem mint változónevet fogja kezelni, hanem mint végrehajtandó parancsot.

Elolvasom a linkeket. Abban tudnál segíteni, hogy mit jelent az, hogy:

"parancssori paraméterek száma"

Elkezd a könyv beszélni erről, de nem magyarázza meg, hogy mit jelent. Van valami köze a 0. argumentumhoz? Erről tudom, hogy maga a programnak a neve. Pl. ha azt írom be, hogy ls -l, akkor az ls a 0. paraméter, az l pedig az első, ugye?

-- Zoli

Ez a script kiírogatja a neki átadott paramétereket külön sorokba. Így jobban látszik, hogy paraméter átadásnál szeparál az egy vagy több szóköz, a neline, a tabulátor, ugyanakkor idézőjelek, aposztrofok egyben tartják a stringet, de literális lesz a szóköz akkor is, ha backslash-sel escape-eled.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Újabb érdekesség:
sh nevű könyvtárat nem tud csak úgy símán eltávolítani, mert parancsnak veszi:

rmdir sh

-- Zoli

Ott valamit elállíthattál.
Esetleg van egy sh nevű aliasod vagy valami ilyesmire tippelek (nincs kéznél linux, amin megnézhetném).
Normálisan nem lenne szabad parancsként megtalálnia, pláne, ha létező könyvtárat akarsz törölni.
Ha nem létezik, akkor valószínűleg beállítottad, hogy a PATH változóban tárolt könyvtárakat nézze végig a nem létező fájlok megtalálásához. (most nem jut eszembe, hogy ehhez mit is kell tenni :( )

az létezhetetlen!
hacsak nem állítottál valamit nagyon el, ahogy elõttem ís írták.
esetleg az IFS változót? vagy a rmdir-t definiáltad fölül?


type rmdir

echo -n "$IFS" | hd

ezeknek a kimenetével kideríthetõ, miért e helytelen mũködés - ha valóban fennáll, amit írsz s nem csak az érzékeid csaltak meg :)

~~~~~~~~
Linux 3.2.0-0.bpo.4-486
Debian 6.0.7

a find program úgy lett leprogramozva, hogy a -name kapcsolót követően "" jelek közé kell tenni, azt amit keresünk, de ha változó [...]

téves, ilyenrõl szó sincs.

a find nem tudja, hogy amikor beírod parancssorba, melyik paraméterét adtad meg literálisan, mleyiket shell változó segítségével, esetleg backtick substitutionnel, stb.
a shell feloldja a magic karaktereket és kifejezéseket ($változó, `backtick`, zárójel, redirection, pipe, escape, stb) és kiforkolja, amit akar(sz) semmi közvetlen információról, arról milyen alakban kapta a program indítására sarkalló parancsot.

a curly bracket használatának a változók szintaktikájában az a létjoga, hogy az ilyen kifejezések egyértelmũek legyenek:


gyum=eper
echo "étel: ${gyum}torta"

tehát, amikor a változó nevét nem követi szóelválasztó jel (pl pont, kötõjel, szóköz), akkor tudassuk a shellel, meddig tart a változó neve.

kezdõ bashinisztáknak javallott bekapcsolni a set -u módot, ami megállítja a script futását, ha inicializálatlan változót kéne feloldania.
(amit sztem elég esetlenül kötetlen változónak magyarítottak)

~~~~~~~~
Linux 3.2.0-0.bpo.4-486
Debian 6.0.7

WTF?

unset a
echo ${a-lo}
echo ${a-lo}

Ha igazad lenne, akkor az elso esetben lo, a masodik esetben viszont mar semmi lenne a kiirt szoveg. Idezek ugyanis a manualbol:

"${parameter:-word}
Use Default Values. If parameter is unset or null, the expansion of word is substituted; otherwise, the value of parameter is substituted. ...
In the parameter expansions shown previously, use of the colon in the format results in a test for a parameter that is unset or null; omission of the colon results in a test for a parameter that is only unset."

Szoval en ugy gondolom maradjunk annyban, hogy nem letezo.

a manual nem mond ellent annak amit én mondok.

${variable-default} esetén nem a variable-t oldja fel (az elõzõ hosszászólásomban helyesebb lenne az, hogy "mielõtt feloldaná"), hanem a zárójelezett kifejezést.

valóban nevezhetjük nem létezõnek az unset változót. csak annyit mondtam, hogy félrevezetõ, mert autovivifikál.

hm, elismerem, hogy csak úgy tunik, hogy autovivifikál. az unset változókat nem létrehozza, csak üres sztringre oldja fel õket (persze +u módban).

~~~~~~~~
Linux 3.2.0-0.bpo.4-486
Debian 6.0.7

Előre éreztem, hogy a zárójelezésbe fogsz belekötni.


bash$ unset a
bash$ set | grep '^a=' # nincs kimenet, mert *nincs* "a" nevű változó
bash$ echo $a

bash$ set | grep '^a=' # még most sincs
bash$ echo $a

bash$ set | grep '^a=' # és még most sincs, ez mehet a végtelenségig

Azaz ha a változó nem létezik, csak attól, hogy hivatkozok rá, nem is lesz. Én ezt mondtam.

Az meg a definíció, hogy ha nem-létező változó értékéről bszélünk (+u üzemmódban), az a semmivel egyenlő. De hogy kényelmesebb(*) legyen :-), nem mindegy, hogy ez a semmi "idézőjelek" között van, vagy nem.


bash$ unset a
bash$ b=1
bash$ echo $b $a $b
1 1
bash$ echo "$b" "$a" "$b"
1  1
bash$ # ugye látszik, hogy a második esetben 2 db. szóköz van a 2 db. egyes között?

Szóval GOTO 1: miért is nem hívjuk a nem-létező változót nem-létező változónak? Főleg, hogy a set -u paranccsal, vagy az általam a korábbiakban emlegetett ${változó:-érték} és ${változó-érték} forma segítségével ha kell, meg is tudom különböztetni a "nem-létezőt" a "létezik-de-üres-sztring-az-értéke" félétől? (Azt amúgy elismerem, hogy nehéz, és általában lényegtelen megkülönböztetni az unset a, és az a="" eredményét, pl. a fenti példában sem látszik a különbség.)

(*) persze ettől pont kényelmetlenebb is tud lenni egyes esetekben.

Ui: ja, és az én bash-omban nem kötetlen változó, hanem "felszabadított változó" a hibaüzenet szerint. Ami baromság.

Ja, és hogy miért nem tetszenek az eddig felmerülő magyarítások az unset-re. Vegyük a C programozási nyelvet. Nem túl elterjedt, de van annak interpretált futtatókörnyezete.

main()
{
int X;
int Y = 1;

printf( "%d\n", X );
printf( "%d\n", Y );
printf( "%d\n", Z );
}

Fenti példácskában X létező, de inicializálatlan. Y létező és inicializált, ellenben Z nem létező változó. Azaz X helyén valami memóriaszemét kéne megjelenjen, Y helyett egy egyes, Z-nél pedig az interpreternek kiabálnia, hogy nincs ilyen változó. Azért ez marhára nem mindegy. És ugyanez a heyzet a bash-nál is, csak éppen ott az interpretált futtatókörnyezet a jellemző, meg nem memóriaszemét lesz X helyén, hanem definit üres sztring, és alapvetően nem anyázik Z helyén.

Azaz GOTO 1: nem kötetlen, nem inicializálatlan, nem Zed-agyában-a-fekete-lyuk, hanem nem-létező változó.

De nyitott vagyok az észérvekre. (Amíg meg nem győztök, én nem-létezőként fogom tanítani.)

Asszociatív tömb használata inkább AWK-ban jellemző. mint shell-scriptben (*). Egyébként remlik, hogy mintha az AWK tényleg úgy működne, hogy a változó (jobbértékként történő) első hivatkozása létre is hozná inicializálatlanként (de meggyőződve nem vagyok róla, sőt a shellel ellentétben azt se tudom hogyan lehetne tesztelni - erre várok tippeket.)

(*) Lefordítom: nem tudok róla, hogy láttam volna élő embert, aki bash-ban asszociatív tömböt használ tudatosan (értsd, nem a néhány beépítve létező változón kívül; mellesleg olyat se, aki ezeket a beépített változókat használta). Igaz, én kevés olyan embert ismerek, aki *tud* sh-ban (pláne bash-ban) programozni. A többség megrémül egy 20 sort, vagy 3-nál több parancsból álló pipe-ot tartalmazó scripttől.

Előfordul mindkettő.

Azt pl. amikor az sh megkerülésére kiszemelt nyelvben a funkcióhoz szükséges modul és annak függőségeinek összeszedése több időt elvisz, mint leírni azt a 20 sort + esetleg belepislantani a manuálba, hogy minden emlék helyes-e, nem tartom racionálisnak, ha nem folyamatos loadot adó kódot legyártásáról van szó.

Szia,

Csak ellen és elrettentő példának jelentkezem :)
Használtam már bashben asszociatív tömböt, mivel egy modem vezérlő script köteget kellett írnom, és az elején (oh, naív tudatlanság :) ) még azt hittem, a /dev/ttyUSB0-t könnyebb írni, mint C/C++-ból portot nyitni. A végére persze mindkettőt megtanultam, de addigra meg túlságosan előrehaladott a "termék", ahhoz, hogy vissza fordulhassak... (+ a belső kísérleti tool hirtelen már megrendelőnek is lett hirdetve... Aminek nagyon-nagyon örültem...)
Viszont, annyi haszna volt az egésznek, hogy elég alaposan sikerült meg ismerkednem a bashel, és a határaival... És, azt kell mondanom, hogy ha nem is a leggyorsabb, legkézenfekvőbb, de azért nagyon hatékony eszköz tud lenni :)

Üdv,
LuiseX

Csak még egy modellt kerestem az elméletibe forduló csevegés tárgyához, hogy (talán) világosabb legyen, hogy ami nincs, az tényleg nincs.

Ami nekünk kifelé nem asszoc tömb a shellben, az befelé definíció szerint az: minden definiált névhez (itt: változóhoz) 1 érték tartozik, csak a megvalósítás a kérdés. Analóg a legtöbb tisztességes nyelvben (pontosan, az AWK nem sorolható ide) megvalósított hash/map/dictionary/ahogyépphívják viselkedésével, amikor a NEM LÉTEZŐ indexre (ami shellben a változónévnek felel) hivatkozva is kapunk egy alapértelmezett valamit, ami "", undef, null, akármi.

- o -

Ahogy írod, az AWK megalkotásakor úgy gondolták, hogy a tömb nem létező elemére hivatkozásakor az elemet létre kéne hozni. A konkatenáció operátortalansága után ezt tartom az 1. számú nyelvi bakinak.

A jelenség kimutatására ugyanaz az "in" operátor szolgál, amit a minap is felhasználtunk, és amit valószínűleg éppen azért tettek képessé a loopon kívüli működésre, mert anélkül nem lehetnénk biztonságban:


$ awk 'BEGIN { print ("auto" in a) ? " van" : " nincs"; a["auto"] ; print ("auto" in a) ? " van" : " nincs" }'
 nincs
 van

Látszik, hogy értéket nem adtam az "auto" indexű elemnek, csak gondoltam rá, és az mégis megszületett.

A fícsör csapdája:


awk 'BEGIN { print ("auto" in a) ? " van" : " nincs"; if (a["auto"]) { FELREVEZETO=1} ; print ("auto" in a) ? " van" : " nincs" }'
 nincs
 van

Az ember akár az "auto" index létezését szeretné ezzel (rosszul) tesztelni, akár az értéket, mellékhatásként rögtön le is gyártja.

Tehát a tiszta út:


awk 'BEGIN { print ("auto" in a) ? " van" : " nincs" ; print ("auto" in a) ? " van" : " nincs"}'
 nincs
 nincs

Önlektorálás: tkp. az "in" loopban használatával is tesztelhető volna a létezés, csak időrabló volna:


for (i in a) { if (i == "keresett_index") {megvan=1; break} }

A szóköz nem semmi, az valami. Mindamellett engem is zavar, a kódot nem igazán teszi olvashatóvá, hogy szóköz a string konkatenáció. Éppen a bash az, ahol semmi az összefűzés, egész egyszerűen a helyettesítés következménye, hogy amit leírunk valahova, az ott lesz. Szerintem ez utóbbi, a valóban semmi operátor jobban áttekinthető, mint a szóköz operátor.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Igazad van. Mentségemre szóljon, ezért hittem a szóközben:

space       String concatenation.

Gyanítom, azért írtak szóközt, mert ha azt írják, semmi a konkatenálás, akkor egyből felvetődik, hogyan fűzünk össze két változóból stringet. Szóközzel szeparálva ez a kérdés is megoldódik, s magyarázni sem kell a manualban.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

"kezdõ bashinisztáknak javallott bekapcsolni a set -u módot"

Sőt, a set -x-et is, hogy szokják, mennyi mindent művel a beírtakkal az interpreter, mielőtt nekilát a futtatásnak.
Bár minden magára valamit adó dok ezzel kezdi az érdemi részt, erős a hajlam annak a pár mondatnak a meg nem emésztésére.

Megjegyzés: a 'sh' nevű fájl kezelése szerintem nem problémás, inkább ezeket javaslom kezdőknek kipróbálásra (tehát nem éles használatra!) mint fájlneveket: ?test1 *test2 ~test3 $test4 -test5 stb (no meg persze a szóközt tartalmazó neveket)

pl: ls -test5 helyett ls -- -test5 a nyerő, touch *test2 helyett touch '*test2'

és 0x0A-t tartalmazó fájlneveket!
a kötõjel kezdetũ fájlnevekre bevált megoldás még így hivatkozni: ./-test5
engem legtöbbször a ~ feloldásával szivat meg a shell.
illetve érdekes, noha a : nem magic karater, a bash auto completion-je kieszképeli.

~~~~~~~~
Linux 3.2.0-0.bpo.4-486
Debian 6.0.7

besegít?
szerintem egy


scp host:./jokai\:aranyember.pdf .

parancssort így hajt végre:


execve("scp", "host:./jokai:aranyember.pdf", ".");

mert nincs mit kieszképelni a :-on.
utána az scp dolgozza fel hogy az 1. arg mit jelent neki.

ennek csak megtévesztõ hatása van (rámnézve az volt, amíg mögé nem láttam a dolgoknak). azt hiheti a user, hogy már a shell is tudja, hogy egy :-ot tartalmazó stringben az 1. tag egy hoszt név, a 2. a path és ha "host:./jokai:aranyember.pdf"-et írna, akkor nem lenne egyértelmũ, hogy "host" vagy "host:./jokai" a hosztnév.

ezt a hatást erõsíti sok terjesztésben a completion függvény foglalkozik vele, hogy az rsync/scp argumentumában van-e hosztnév (*), de ez nem beépített képessége a shellnek, úgy mint a wildcardok feloldása. ezért eshet orcára az ember, amikor nem oldja fel a shell neki az


scp host:./*.pdf .

parancsban a *-ot.

(*)
ennek külön tudok örülni, mert reflexbõl nyomogatom a TAB-ot és távoli, lassú elérésũ gépekrõl való scp-zésnél mindig megtorpan az élet, amíg a completion fgv a háttérben bemászik a gépre és fájllistát kér.

~~~~~~~~
Linux 3.2.0-0.bpo.4-486
Debian 6.0.7

Olyan nemrég történt - és én teljesen kezdő vagyok bash ből, meg minden programozási nyelvből -, hogy gondot okoztak a szóközt tartalmazó fájlnevek. Ha jól emlékszem for ciklusban, én azt akkor úgy oldottam meg, hogy előtte kicseréltem az összes fájlban a szóközöket _ ra, és máris jó lett:-)

Amiket írkáltok az nekem most magas:-)

-- Zoli

És ha pont is van a filenévben? Arra céloztam, hogy óvatosan a cserékkel, mert lehet, visszafelé már nem tudod megcsinálni. Lehetne például hasonló technikát alkalmazni, mint amit a shell csinál: escape-elni. Ezzel az a baj, hogy nő a filenév hossza, az meg baj, mert többnyire 255 byte lehet.

Tehát csináld jól! Az egyik lehetőség, hogy idézőjelek között használod fel a filenevet, a másik, hogy módosítod az IFS értékét.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE