[BSD] kis sed segitseg

Zahemszky Gábor Gabor at Zahemszky.HU
2012. Okt. 1., H, 08:29:27 CEST


Szia!

Bocsánat, most néztem, hogy figyelmetlen voltam. Noha jól írtam a
magyarázatot :-), nem vettem észre, hogy Mauzi válasza hibás, és nem
írtam le a jó választ (illetve a több lehetséges közül egyet sem).

Nézzük pontosan: (egy fontos közbeszúrás: a promnpt éls a hibaüzenet
formája alapján 100%-ra meg vagyok győződve, hogy ez is csh és nem sh.)

> % sed -i "s/\"/'/g" /tmp/example.txt
> Unmatched '.

Itt (meg már az eredeti megoldásodnál is) van egy hiba, ami fölött
teljesen elsiklottam. Az pedig a -i opció, aminek *kell* egy paraméter,
ami megadja, hogy mi legyen a backup fájl kiterjesztése. BSD alatt
(Linux most fejből nem ugrik be, de ha kell megnézhető) - ezt a
paramétert vagy hozzáragasztod a -i opcióhoz így: -ibak , vagy külön
írod így: -i bak , de ha el akarod hagyni (azaz in-place cserét
akarsz), azt csak oly módon teheted meg, hogy *külön* írod a semmi
sztinget: -i '' (azaz expliciten adsz egy paramétert, ami az üres
sztring - ezt nyilván '' vagy akár "" formában is megteheted). Mivel ez
nálad elmarad, ezért a sed művelet ( s/...) lesz a kiterjesztés. Ez a
hiba az eredeti és a mostani példáidnál is fennáll, de ez csak egyike a
zűrzavar okozóinak, ugyanis a többségben el sem jutunbk odáig, hogy ez
kiderüljön.

Fenti első példádban a következő hiba, ami miatt a (C-) shell reklamál,
hogy idézőjelen belül az aposztrófot is takarni kell; mivel ez nem
történt meg, hanem van egy nyitó aposztróf, aminek nincs párja, ezt
jelzi a hibaüzenet is.

Hogy a dolog még bonyolultabb legyen, ebben nem igazán egységesek a
shellek, ráadásul a csh még külön agyament módon dolgozik. Itt a példa:

$ csh
% echo "a'b"
a'b
% echo "s/\"/'/g" /tmp/example.txt
Unmatched '.
% echo "s/x/'/g" /tmp/example.txt
s/x/'/g /tmp/example.txt
%

Remélem látszik, hogy az első és a harmadik példában nem kellett
eltakarnom, ment a nélkül is, a másodikban viszont kellett volna.

> A csh is reklamal:

Csak piszkálódásképpen, mint fent írtam már: a fenti is csh, amit a
prompt csak sugall, de a hibaüzenet egyértelműsít.

> % echo  "s/\"/'/g"
> Unmatched '.

Tökre mint az előbb, csak nincs meg a fájlnév, és szerencsére kimaradt
az amúgy is problémát okozó -i opció.

> A bash teljesen beteg lesz:
> 
> $ % echo "s/\"/'/g"
> bash: fg: %: no such job

Nem csoda, mert a copy-and-paste-be belekerült az előző csh promptja
is, a % viszont kb minden shell-ben un. job-id hivatkozás, amihez *kell*
mögé egy szám, vagy pár betű, ilyesmi.

> A bash+sed paros meg inkabb:
> 
> $ sed -i "s/\"/'/g" example.txt
> sed: 1: "example.txt": invalid command code e

No itt már jól látszik a hibaüzenetből, hogy a -i után álló izé lenne a
kiterjesztés, tehát az example.txt -t tekinti sed parancsnak.

Thát szerintem a helyes megoldás:

$ sed -i "" -e "s/\"/'/g" example.txt

Vizont a tesztek azt mutatják, hogy csh esetén még mindig nem jó, mert
az idézőjelen belüli \" páros megfekszi a hasát. Így itt a következő:

% sed -i "" -e 's/"/'"'/g" example.txt

Azaz az első felében az idézőjelet aposztrófok közé teszed, a
másodikban az aposztrófot pedig idézőjelek közé. :-D Ez utóbbi megoldás
két előnnyel is rendelkezik:
- FreeBSD alatt megy sh / csh / bash / ksh alatt is
- soha többet nem rúgnak ki, mert rajtad kívül nem sokan fogják tudni
  megemészteni a kódodat.

No jó szórakozást és kellemes hányást a shellek rejtelmeihez :-)

Zahy

-- 
#!/bin/ksh
#
# See my GPG key at http://www.Zahemszky.HU
#
Z='21N16I25C25E30, 40M30E33E25T15U!';
IFS=' ABCDEFGHIJKLMNOPQRSTUVWXYZ ';
set -- $Z;for i;{ [[ $i = ? ]]&&print $i&&break;
[[ $i = ??? ]]&&j=$i&&i=${i%?};
typeset -i40 i=8#$i;print -n ${i#???};
[[ "$j" = ??? ]]&&print -n "${j#??} "&&j=;typeset +i i;};
IFS=' 0123456789 ';set -- $Z;for i;{ [[ $i = , ]]&&i=2;
[[ $i = ?? ]]||typeset -l i;j="$j $i";typeset +l i;};print "$j"



További információk a(z) BSD levelezőlistáról