változók láthatósága - bash

Kedves Fórumozók!

A következő shell scriptet faragom éppen, de nem akar úgy működni, ahogy azt én szeretném:


OK=0
WARNING=1
CRITICAL=2
UNKNOWN=3

append() {
	if [ -z "$1" ]; then
		echo "$2"
	else
		echo "$1; $2"
	fi
}
FS_USAGE=$( df -k | /usr/xpg4/bin/grep -E "swap|^/dev" | awk '{print $6,$5;}' )

FS_PAIRS=$( echo "$FS_USAGE" | tr -d % )

WARNINGS=""
CRITICALS=""

echo "$FS_PAIRS" | while read FS US; do
	echo "$FS:$US"
	if [ "$US" -gt 5 ]; then
		echo "FIRED!"
		CRITICALS=$( append "$CRITICALS" "$FS: $US%!!!" )
		echo "$CRITICALS"
	else
		if [ "$US" -gt 85 ]; then
			WARNINGS=$( append "$WARNINGS" "$FS: $US%" )
		fi
	fi
done
echo "/${CRITICALS}/"
echo "/${WARNINGS}/"
echo "/$CRITICALS/"
echo "/$WARNINGS/"

if [ "$CRITICALS" ]; then
	echo "CRITICAL: $CRITICALS"
	exit $CRITICAL
fi

if [ "$WARNINGS" ]; then
	echo "WARNING: $WARNINGS"
	exit $WARNING
fi

echo "OK"
exit $OK

A kimenet pedig:


/:3
/boot:10
FIRED!
/boot: 10%!!!
/var:11
FIRED!
/boot: 10%!!!; /var: 11%!!!
//
//
//
//
OK

A problémám pedig konkrétan az, hogy a CRITICALS, és WARNINGS változóba szeretném gyűjteni az adott felhasználási küszöbnél magasabb telítettségű FS-eket, és a ciklus vége után a szkript mintha "elfelejtené" a változó tartalmát. Ez valószínűleg azért lehet, mert egy azonos nevű lokális változót hoz létre a script, és a blokk vége után az értéke elveszik.
De itt nem függvényről, vagy szubrutinról van szó, és a változót direkt létrehoztam még a ciklus előtt. Amúgy is egy blokkon belül alapból nem kellene lokális változót létrehoznia, csak akkor, ha előtte a local kulcsszót használom. Nemde? Hogy van ez?

Hozzászólások

röviden: a subshell-ben módosított változó érték nem kerül "fel" automatikusan a subshell-t indító környezetbe.

De a ciklusmag a fenti esetben miért subshellben fut? Alapból nem gondolom, hogy ezt kellene tenni neki...
-------------------------------------------------------------------------------
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 13.37 | 2.6.39.4-janos

Azt hiszem, az igazi választ megadták.
Annyit tennék hozzá, hogy annak nyomós oka kell, hogy legyen, hogy az ember feldolgozásra visszatérjen a shellbe, ha már egyszer elindított egy awk processzt - itt nem látok ilyen okot.

De, ha fenntartás nélkül neki mersz menni ilyen shellszkriptnek, akkor az vagy, csak nem tudsz róla, mert (valószínűleg) nem tudod, hogy az awk nyelvi lehetőségeit átfutni es awk-programot írni MESSZE kisebb erőfeszítés, mint báremlyik shellből kihozni valami hasznosat.
De most már tudod.

Igen, nagyjából tisztában voltam vele, hogy az awk sokkal többre képes mint, amire használom. Azt is írják róla, hogy inkább egy egész programnyelv, mint egy egyszerű parancs.
Egyelőre csak egy sed tutorialt olvastam el, az awk mélyebb/alaposabb áttanulmányozására még nem tudtam sort keríteni.
-------------------------------------------------------------------------------
Az életben csak egy dolog a szép, de az épp nem jut eszembe.

Slackware Linux 13.37 | 2.6.39.4-janos

Valami ilyesmi..?


echo "$FS_PAIRS" | (
  while read FS US; do
	echo "$FS:$US"
	if [ "$US" -gt 5 ]; then
		echo "FIRED!"
		CRITICALS=$( append "$CRITICALS" "$FS: $US%!!!" )
		echo "$CRITICALS"
	else
		if [ "$US" -gt 85 ]; then
			WARNINGS=$( append "$WARNINGS" "$FS: $US%" )
		fi
	fi
  done

  echo "/${CRITICALS}/"
  echo "/${WARNINGS}/"
  echo "/$CRITICALS/"
  echo "/$WARNINGS/"

  if [ "$CRITICALS" ]; then
	echo "CRITICAL: $CRITICALS"
	exit $CRITICAL
  fi

  if [ "$WARNINGS" ]; then
	echo "WARNING: $WARNINGS"
	exit $WARNING
  fi
)
retval=$?
[ $retval -gt 0 ] && exit $retval

vagy


set -e

echo "$FS_PAIRS" | (
  while read FS US; do
	echo "$FS:$US"
	if [ "$US" -gt 5 ]; then
		echo "FIRED!"
		CRITICALS=$( append "$CRITICALS" "$FS: $US%!!!" )
		echo "$CRITICALS"
	else
		if [ "$US" -gt 85 ]; then
			WARNINGS=$( append "$WARNINGS" "$FS: $US%" )
		fi
	fi
  done

  echo "/${CRITICALS}/"
  echo "/${WARNINGS}/"
  echo "/$CRITICALS/"
  echo "/$WARNINGS/"

  if [ "$CRITICALS" ]; then
	echo "CRITICAL: $CRITICALS"
	exit $CRITICAL
  fi

  if [ "$WARNINGS" ]; then
	echo "WARNING: $WARNINGS"
	exit $WARNING
  fi
)

Ez utóbbi szebb, de óvatosabbnak kell lenni vele..

sub
--
>'The time has come,' the Walrus said<

Ha muszáj a pipe, akkor úgy hozok ki belőle változót, hogy a pipe-on belül stdout-ra küldöm a cuccot, s az egész kócerájt egy helyettesítésként változó értékadásába írom valahogy emígyen:

#!/bin/bash

eval "a=(`ls -l |\
    while read; do
        echo \"'$REPLY' \"
    done`)"
echo "${a[5]}"
exit 0

Tudom, rondácska egy értékadás, ugyanakkor egyből egy tömböt definiál, így sok változót ki lehet így hozni. Valami ilyesmi lesz belőle:

a=('érték0' 'érték1' 'érték2' 'érték3' 'érték4' 'érték5' )

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

Több kérdésem is van:
- minek az eval?
- minek a pipe jel után a backslash? (eleve idézójelben vagy, onnan meg mindenestül kiesik; de ha nem lennél idézőjelen belül, akkor se kéne)
- biztosan ls -l -et akartál írni? Mert szerintem akkor lesz benne egy halom fölösleges szemét, nem csak fájlnevek, de tény, ez akár kellhet is.) Úgyhogy koncentráljunk az első két kérdésre.

Ha nincs eval, akkor az 'a' változó értéke egy string lesz, ami

('érték0' 'érték1' 'érték2' )

szerkezetű, de ez csak egy string. Nekünk viszont az kell, hogy a shell kifejtse ezt, mintha leírtuk volna így, mint tömb definíciót:

a=('érték0' 'érték1' 'érték2' )

Igazad van abban, hogy a pipe után nem kell a backslash. Azért írtam, hogy egy sorban legyen a while-lal, a newline karaktert ignorálja. Valahogy az volt bennem, egy sorba írnám, de az átláthatóság miatt mégis több sorban írom.

Általában akartam megmutatni, hogy pipe-ból, azaz másik process-ből, elszeparált memóriaterületről bash-ben hogyan lehet több változót kihozni a szülő shellhez. Azért választottam az ls -l parancsot, hogy az eredményben legyenek szóközök is, s látszodjék, a szóközöket tartalmazó értékek átadása is sikeres. Persze, hogy az, hiszen a $REPLY köré tettem literális aposztrofokat, amelyek az eval kapcsán a shelltől teljes elzárásként fog értelmeződni, persze mindez már a $REPLY helyettesítésének megtörténte után.

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

Még mindig nem értek egyet.

a=(`ls -l`)
eval a=(`ls -l`)

nekem tök ugyanazt az eredményt adja(*), megfejelve azzal, hogy ha az ls kimenetében lennének spéci karakterek, akkor az általam javasolt evaltalan formában megmaradnának a tömbváltozó valamely elemének értékeként, míg eval esetén számíthatsz egy további feldolgozásra. (Azt meg nem látom, hogy az én ls-példám, és a te sokkal bonyolultabb parancsod között az adott esetben mitől lenne különbség.) Elvben meggyőzhető vagyok, de a fenti okfejtésed nekem nem OK.

(*) azaz a és aa nem egy string értékű változó, hanem egy tömbváltozó egymillió elemmel.

Szerk: szerintem akkor lenne igazad a sztringben, ha a zárójelek el lennének takarva.

Igyekeztem minimalista szinten változtatni a te kódodon, de nekem látszólag megy, azaz a teljes ls kimenet minden szava egy tömbelem.

#!/bin/bash

a=(`ls -l |
while read; do
echo "$REPLY"
done`)
printf '!!! %s !!!\n' "${a[@]}"
exit 0

Ha neked az kell, hogy a szóközöknél ne essen szét, hanem csak a soremeléseknél, akkor sokkal egyszerűbb megoldás az értékadás előtt átállítani az IFS változót:

#!/bin/bash

IFS='
'
a=( ...

mondjuk így.

Persze, hogy nem minden szónak kell egy tömbelemnek lennie. A példában gondolom látszott, hogy egy-egy változót egy-egy echo paranccsal írtam a tömbbe, ahol a változó értéke tartalmazhat szóközt is. Éppen ezért írtam a $REPLY köré aposztrofokat. Az eval-t pedig éppen arra használtam, hogy az első alkalommal generálom a stringet:

a=('value 0'
'value 1'
'value 2'
)

A value n értékek jelen példámban a $REPLY változóból kerülnek a stringbe. Erre ráengedve az eval-t, a shell ezt megkapja értelmezésre, s így definiálásra kerül egy tömb, melynek elemei aposztrofok által határolt értékek, s éppen ezért szóköz, tabulátor, newline mentén nem esik szét az érték.

Nem tudom, az IFS átdefiniálása mennyivel egyszerűbb, nekem nem tűnik annak az eval-lal szemben. Ugyanakkor töredelmesen bevallom, hogy azért sem szoktam hozzányúlni, mert nem tudom, pontosan mire is vonatkozik az IFS. Kipróbáltam, magára a scriptre, tehát arra, hogy az interpreter felolvassa a scriptet, nincs hatással. Ezek szerint a read-re vonatkozik.

Ugyanakkor én pipe-ból való változó kihozására írtam általános példát, arról szó sincs, hogy szóközt tartalmazó változó read által keletkezik, lehet az úgy is, hogy pipe-ból read-del olvassuk a sort, majd valamilyen algoritmussal előállítunk egy változót, amelyik szóközt tartalmaz.

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

Elnézést, hogy ide ugatok, de...
Az IFS, teljes nevén Internal Field Separator, puritán fordítva "belső mező elválasztó". Amikor átírod, az utána jövő sorokban ez fog mező elválasztóként funkcionálni tömböknél, amikor a bash használja az adott változót (tévedés joga fenntartva, én így értelmeztem, eddig :) Ha tévedek, javítsatok :) )
Lényegében a bash lelki világán nem változtat, a felhasználáson kényelmesít, a "beolvasott értékekre" van kihatással, pontosabban definiálod, hogy mi legyen utána a tömbhatárolónak vett karakter :)
(Alapértelmezetten az IFS értéke tetszőleges whitespace, amibe beleesik a new-line is... Ezért nem is szükséges a visszatérési értéknél átállítani, ha sorokba rendezve adod át az outputodat :) )
Egy egész kellemes kis leírás található róla itt : http://tldp.org/LDP/abs/html/internalvariables.html

Probléma mentes használásához célszerű először lementeni egy másik változóba az aktuális értéket, majd utólag visszaállítani, pl.:

OLD_IFS=IFS
IFS="
"
#A részlet, ahol kell nekünk az átértelmezés
IFS=OLD_IFS

Leginkább az IFS használata, számomra, olyankor kellemes, amikor "egy változóba" akarok több paramétert zsúfolni, és utána kényelmesen értelmezni ezt.
Inkább összetett script kötegeknél van értelme - én például egy plugin szerűség implementálásánál használtam fel -, vagy, amikor pl. egy csv jellegű filet akarsz kényelmesen kezelni :)
Üdv,
LuiseX

Egyébként most látom, hogy akkor csinálná pontosan azt, amit írtam, ha

echo -n \"'$REPLY' \"

Lenne az a bizonyos sor. Másik lehetőségként, ha a tömb elemei között newline karakterrel szeparálok, felesleges a szóköz, tehát jó az

echo \"'$REPLY'\"

is. Persze, ahogy eredetileg írtam, úgy is jó.

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

Van egy trükk while-nél... így próbáld meg:


 while read FS US; do
	echo "$FS:$US"
	if [ "$US" -gt 5 ]; then
		echo "FIRED!"
		CRITICALS=$( append "$CRITICALS" "$FS: $US%!!!" )
		echo "$CRITICALS"
	else
		if [ "$US" -gt 85 ]; then
			WARNINGS=$( append "$WARNINGS" "$FS: $US%" )
		fi
	fi
 done  < <(echo "$FS_PAIRS")