[WORKED AROUND] Bash változó kiértékelése olvasáskor

Röviden a következőt szeretném megvalósítani:


$ A='foo=$B' #1
$ B=123      #2
$ echo $A    #3
foo=123
$

Meg tudom csinálni pl. printf-fel, de szeretném tudni van-e lehetőség arra, hogy #3 változatlan maradjon.

Kicsit hosszabb példa, csak hogy érthetőbb legyen mit szeretnék elérni:

hello.sh:


#!/bin/bash

# DON'T TOUCH THIS!

echo "$HELLO_MESSAGE"

Normál használat:


$ export HELLO_MESSAGE='Hello, World!'
$ hello.sh
Hello, World!
$

Amit én szeretnék látni:


$ export HELLO_MESSAGE='Hello, $WHO!'
$ WHO='HUP' hello.sh
Hello, HUP!
$

Hozzászólások


eval "echo $A"

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

Lehet, nem értettem meg, mit értesz változatlanságon. Mi a feladat?

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

Van egy shell script, amihez nem akarok nyúlni (mivel git által trackelt fájl), ami használ pár környezeti változót. Az egyik egy viszonylag hosszú string, amiben van egy paraméter, amit szeretnék változtatni hogy tudjam futtatni a scriptet egyszerre több példányban. Nyilván bele fogok nyúlni a konkrét változóba, de nem akarom a teljes változót n példányban lemásolni, hanem azt szeretném ami megmaradna mint globális változó, és annak lenne egy paramétere, ami egy másik (nem globális) változóból jönne.

Tehát a fenti példából: A globális, csak egyszer szeretnék neki értéket adni (#1), B-t mindegyik futtatás előtt változtatnám (#2), #3 pedig egy scriptben van amit nem szeretnék megváltoztatni.

echo $A-ra azt adja, hogy "foo=$B" a foo=123 helyett lenne az elvárt működés?

MikroVPS

A="foo=$B"

De nem kizárt, hogy félreértettem a problémát.

-----

(&%;_98\<|{3W10Tut,P0/on&Jkj"Fg}|B/!~}|{z(8qv55sr1C/n--k**;gfe$$5a!BB]\.-

$ cat somescript_helper
#!/bin/sh

TMPNAME="somescript.$$"

sed 's/foo/bar/g' somescript >"$TMPNAME"

exec "$TMPNAME"

Amit keresel, ahhoz nagyon hasonló az un. nameref. Ím a példa:


$ unset a b
$ typeset -n a=b
$ echo $a

$ b=3
$ echo $a
3
$ b=4
$ echo $a
4
$

A funkció a ksh93-ban jelent meg először (tudtommal), de átvették a bash-ban is. (bash-ban éppen írhatnád a typedef helyett a declare parancsot is, de akkor kevésbé lenne hordozható) Hogy a feladatodhoz tudod-e használni, azt nem tudom.

Gondolkoztam egy kicsit a dolgon, és nem nagyon látom, hogy ezt az eredeti kód módosítása nélkül hogyan tudnád használni. Ha meg módosítod a ködot, akkor már kevésbé macerás megoldást is használhatsz.
Tegyük fel, kell egy ciklus amit 0-tól "a"-ig akarsz futtatni, de a célod az, hogy hol így, hol úgy fusson. Ekkor az első verzió nyilván valahogy így nézne ki:


$ cat lala.sh
a=4
i=0
while (( i < a )) ; do
	echo $i
	(( i = i + 1 ))
	done

(Nyilván sok mindent lehet másként, most ne arra koncentráljunk.) Ha módosítani akarod a ciklus futását, akkor a kezdő értékadást átírod. Fent emlegtett nameref-fel persze látszólag egyszerűbb, a kezdeti módosítás után sosem kell hozzányúlni a kódhoz (igaz, cserébe a futtatás macerásab lett):


$ cat lala.sh
typeset -n a=b
i=0
while (( i < a )) ; do
	echo $i
	(( i = i + 1 ))
	done
$ env b=3 ksh93 lala.sh
0
1
2
$ env b=5 bash lala.sh
0
1
2
3
4

Igazából ezzel az a baj, hogy ilyen erővel át lehetne írni úgy a kódot, hogy eleve a kívülről jövő "b" környezeti változót kezelje (és ne ilyen nyakatekerten) - sőt akár magát a kezdeti értéket is vehetné kívülről.


a="$b"

(esetleg simán kimarad az inicializálás, és csinálj "a" nevű környezeti változót.)
Vagy csak simán az első paraméterben megkapod "a" értékét, aztán shift-elsz egyet a paramétereken, és minden megy az eredeti módon.


a="$1" ; shift

Ekkor persze a hívást megint változtatni kell (a legeslegelsőhöz képest)
A végeredmény kb ugyanaz, de a nameref egy sokkal kevésbé hordozható (pláne ha tényleg csak 4.3-as bash-ban jelent meg), sokkal kevesebbek által ismert lehetőség. De használatához ugyanúgy kell mindent (a kódot és a futtatást) változtani, mint az előző két módszerhez. Tudtommal a nameref "tulajdonság" nem öröklődik shellek között (persze a Shellshock óta sok mindent el tudok képzelni) - így a módosítást azzal sem úszod meg.

Ha nem örökli, akkor sajnos tényleg nem jó.

Közben végiggondoltam hogy egy egysoros scripttel egész kulturáltan meg lehet valósítani (lásd topicindító hozzászólás):

myhello.sh:


#!/bin/sh

HELLO_MESSAGE="Hello, $WHO" hello.sh

Használat:


$ WHO='HUP' myhello.sh
Hello, HUP!
$

A gond ezzel az, hogy a HELLO_MESSAGE WHO-s alakja csak ebben a scriptben van definiálva, nem egy globális változó ezért a hello.sh-t közvetlenül elindítva nem hat rá a WHO változó továbbra sem. Ezzel épp együtt tudok élni, de nem érzem szép megoldásnak.

Vagy ahogy Zahy írta, vagy így:


$ A='foo=$B'
$ echo $A
foo=$B
$ B=123
$ echo $A
foo=$B
$ eval A="$A"
$ echo $A
foo=123
$

Ezzel az a baj hogy kell egy lépés a script meghívása előtt, amit már abban a shellben kell végrehajtani amiből a script meghívásra kerül. Kb. ugyanezt meg tudom valósítani a printf-fel is, amire eredetileg gondoltam:


$ A='foo=%s'
$ B=123
$ A=$(printf $A $B)
$ echo $A
foo=123
$

Lusta vagyok vegiggondolni, csak egy otlet.
Nem az 'export -f'-et keresed?

Más megoldás nem jöhet szóba?

pl. lehet készíteni egy új scriptet, amely lemásolja a fájlt, majd pl. sed-el kicserélni az adott részt. Az újonnan létrejött fájlokat .gitignore-ba lehet rakni/törölni. (esetleg eval...)

Eddig a következő one-linerre jutottam:


$ A='foo=%s'
$ B=123
$ A=$(printf "$A" "$B") eval 'echo $A'
foo=123
$ echo $A
foo=%s
$

Az eval csak akkor kell, ha a sorban később szerepel a $A, script vagy függvény meghívásakor nem szükséges.

Az eredeti problémámat ebben a formában sajnos még nem oldja meg, mivel csak környezeti változókat és paramétereket tudok beállítani mielőtt a script meghívásra kerül, amibe nem fér bele a $(). Ezt viszont már tényleg könnyen át tudom hidalni egy egyszerű scripttel. Hacsak a printf nem akarná értelmezni az inputban szereplő kapcsolónak kinéző stringeket kapcsolónak... Ha más is belefutna: a

printf -- formatstring params...

működik.

Lehet, hogy nem értem a problémát teljesen,
De nem az input paramétert keresed?
Hívás: hello.sh "alma"

az shban pedig:
$HELLO_MESSAGE = $1

Végül a következő megoldást alkalmaztam (topicindító hozzászólás második példáján levezetve):

Lett egy új környezeti változóm, a HELLO_MESSAGE_TEMPLATE. Már ebből hozom létre a HELLO_MESSAGE-et amiből lesz egy default, így:


export HELLO_MESSAGE_TEMPLATE='Hello, %s!'
export HELLO_MESSAGE=$(printf -- "$HELLO_MESSAGE_TEMPLATE" 'World')

Lett egy új script, ami ha létezik, behelyettesíti a megadott paramétert:


#!/bin/sh

if [ -n "$WHO" ]; then
	HELLO_MESSAGE=$(printf -- "$HELLO_MESSAGE_TEMPLATE" "$WHO") hello.sh "$@"
else
	hello.sh "$@"
fi

Így a következő módon lehet futtatni az eredeti scriptet:


$ hello.sh		# eredeti változat
Hello, World!
$ myhello.sh		# saját script, de az eredeti eredményt adva
Hello, World!
$ WHO='HUP' myhello.sh	# saját script, paraméteres változóbehelyettesítés
Hello, HUP!
$

Egy kis munkával egész általánosra meg lehetne csinálni, de ehhez most már nincs kedvem.

(A workaround egy probléma megkerülése: főnév. Megkerülve a probléma: worked around. A workarounded olyasmi, mint a felásózott kert, vagy a kikalapácsolt kasztni. VISZONT: sokáig a telnet meg az ftp is csak főnév volt, aztán ki nem szarta le. Szóval sose legyen nagyobb szégyened, max. picit előrébb jársz, mint az angol méjnsztrím.)