W niektórych powłokach (w tym bash
):
IFS=: command eval 'p=($PATH)'
(za pomocą bash
można pominąć command
emulację sh / POSIX). Ale uważaj, że gdy używasz zmiennych niecytowanych, to również na ogół musisz set -f
, i nie ma takiego zasięgu lokalnego w większości powłok.
Zsh możesz zrobić:
(){ local IFS=:; p=($=PATH); }
$=PATH
wymusza dzielenie słów, które nie jest domyślnie wykonywane w zsh
(globowanie przy rozszerzaniu zmiennych również nie jest wykonywane, więc nie potrzebujesz, set -f
chyba że w emulacji).
(){...}
(lub function {...}
) są nazywane funkcjami anonimowymi i zwykle są używane do ustawiania zasięgu lokalnego. z innymi powłokami, które obsługują lokalny zasięg funkcji, możesz zrobić coś podobnego z:
e() { eval "$@"; }
e 'local IFS=:; p=($PATH)'
Aby zaimplementować zasięg lokalny dla zmiennych i opcji w powłokach POSIX, możesz także użyć funkcji podanych na stronie https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh . Następnie możesz użyć go jako:
. /path/to/locvar.sh
var=3,2,2
call eval 'locvar IFS; locopt -f; IFS=,; set -- $var; a=$1 b=$2 c=$3'
(nawiasem mówiąc, niepoprawne jest dzielenie w $PATH
ten sposób powyżej, z wyjątkiem tego, zsh
że w innych powłokach IFS jest separatorem pól, a nie separatorem pól).
IFS=$'\n' a=($str)
To tylko dwa zadania, jedno po drugim, tak jak a=1 b=2
.
Uwaga na temat var=value cmd
:
W:
var=value cmd arg
Powłoka wykonuje się /path/to/cmd
w nowym procesie i przechodzi do cmd
oraz arg
do argv[]
i var=value
do envp[]
. To nie jest tak naprawdę przypisanie zmiennych, ale więcej przekazywania zmiennych środowiskowych do wykonywanego polecenia. W powłoce Bourne'a lub Korna set -k
możesz nawet napisać cmd var=value arg
.
Teraz nie dotyczy to wbudowanych funkcji lub funkcji, które nie są wykonywane . W Bourne shell, w var=value some-builtin
, var
kończy się ustawiony jest potem, podobnie jak w var=value
spokoju. Oznacza to na przykład, że zachowanie var=value echo foo
(które nie jest przydatne) różni się w zależności od tego, czy echo
jest wbudowane, czy nie.
POSIX i / lub ksh
zmieniło to, że zachowanie Bourne'a występuje tylko dla kategorii wbudowań zwanych wbudowanymi specjalnymi . eval
jest specjalnym wbudowanym, read
nie jest. W przypadku niespecjalnego wbudowanego, var=value builtin
zestawy var
tylko do wykonania wbudowanego, dzięki czemu zachowuje się podobnie jak podczas wykonywania zewnętrznego polecenia.
command
Komenda może być używany do usuwania szczególną cechę tych szczególnych poleceń wbudowanych . POSIX przeoczył jednak to, że dla wbudowanych eval
i .
, oznaczałoby to, że powłoki musiałyby implementować stos zmiennych (nawet jeśli nie określa poleceń ograniczających zakres local
lub typeset
zakres), ponieważ można wykonać:
a=0; a=1 command eval 'a=2 command eval echo \$a; echo $a'; echo $a
Lub nawet:
a=1 command eval myfunction
z myfunction
funkcją używającą lub ustawiającą $a
i potencjalnie wywołującą command eval
.
To było naprawdę przeoczenie, ponieważ ksh
(na której opiera się głównie specyfikacja) nie wdrożyło go (i AT&T ksh
i zsh
nadal tego nie robi), ale obecnie, z wyjątkiem tych dwóch, większość powłok go implementuje. Zachowanie różni się w zależności od muszli, chociaż może wyglądać tak:
a=0; a=1 command eval a=2; echo "$a"
chociaż. Używanie local
w powłokach, które obsługują, jest bardziej niezawodnym sposobem na wdrożenie zasięgu lokalnego.
IFS=: command eval …
ustawiaIFS
tylko na czas określonyeval
przez POSIX w dash, pdksh i bash, ale nie w ksh 93u. To niezwykłe, że ksh jest dziwnie niezgodny.Standardowe zapisywanie i przywracanie pochodzi z „The Unix Programming Environment” Kernighana i Pike'a:
źródło
$IFS
poprawnie, jeśli wcześniej było rozbrojone.$'\t\n'' '
, jak wyjaśniono tutaj: wiki.bash-hackers.org/syntax/expansion/...$' \t\n'
. przestrzeń musi być pierwsza, ponieważ jest używana"$*"
. Zauważ, że jest tak samo we wszystkich pociskach podobnych do Bourne'a.Umieść skrypt w funkcji i wywołaj tę funkcję, przekazując mu argumenty wiersza poleceń. Ponieważ IFS jest zdefiniowany lokalnie, zmiany w nim nie wpływają na globalny IFS.
źródło
Dla tego polecenia:
Istnieje alternatywne rozwiązanie: dać pierwszemu przypisaniu (
IFS=$'\n'
) polecenie do wykonania (funkcję):Spowoduje to umieszczenie IFS w środowisku w celu wywołania podziału, ale nie zostanie zachowane w obecnym środowisku.
Pozwala to również uniknąć zawsze ryzykownego użycia eval.
źródło
$IFS
ustawione na$'\n'
później, zgodnie z wymaganiami POSIX.Proponowana odpowiedź z @helpermethod jest z pewnością interesującym podejściem. Ale to także trochę pułapka, ponieważ w zmiennej BASH zakres zmiennej lokalnej rozciąga się od wywołującego do wywoływanej funkcji. Dlatego ustawienie IFS w main () spowoduje utrwalenie tej wartości w funkcjach wywoływanych z main (). Oto przykład:
A wynik ...
Gdyby IFS zadeklarowany w main () nie był jeszcze objęty zakresem w func (), to tablica nie zostałaby poprawnie przeanalizowana w func () B. Odkomentuj pierwszy wiersz w func () i otrzymasz ten wynik:
To jest to, co powinieneś uzyskać, jeśli IFS wyszedł poza zakres.
O wiele lepszym rozwiązaniem IMHO jest rezygnacja ze zmiany lub polegania na IFS na poziomie globalnym / lokalnym. Zamiast tego spawnuj nową powłokę i baw się z IFS. Na przykład, jeśli wywołasz func () w main () w następujący sposób, przekazując tablicę jako ciąg znaków z separatorem pól ukośnika do tyłu:
... ta zmiana na IFS nie zostanie odzwierciedlona w func (). Tablica zostanie przekazana jako ciąg:
... ale wewnątrz func () IFS nadal będzie miał wartość „/” (jak ustawiono w main ()), chyba że zostanie zmieniony lokalnie w func ().
Więcej informacji na temat izolowania zmian w IFS można znaleźć pod następującymi linkami:
Jak przekonwertować zmienną tablicy bash na ciąg rozdzielany znakami nowej linii?
Bash ciąg do tablicy za pomocą IFS
Wskazówki i porady dotyczące ogólnego programowania skryptów powłoki - patrz „UWAGA użycia podpowłoki ...”
źródło
IFS=$'\n' declare -a astr=(...)
idealne dzięki!Ten fragment z pytania:
jest interpretowany jako dwa oddzielne przypisania zmiennych globalnych ocenianych od lewej do prawej i jest równoważny z:
lub
To wyjaśnia zarówno, dlaczego zmieniono globalny
IFS
, jak i dlaczego podział słów$str
na elementy tablicowe przeprowadzono przy użyciu nowej wartościIFS
.Możesz ulec pokusie użycia podpowłoki, aby ograniczyć efekt
IFS
modyfikacji w następujący sposób:ale szybko zauważysz, że modyfikacja
a
jest również ograniczona do podpowłoki:Następnie miałbyś ochotę zapisać / przywrócić IFS, korzystając z rozwiązania z poprzedniej odpowiedzi @msw lub spróbować użyć funkcji
local IFS
wewnętrznej, jak sugeruje @helpermethod. Ale wkrótce zauważasz, że masz wiele kłopotów, zwłaszcza jeśli jesteś autorem biblioteki, który musi być odporny na źle działające skrypty wywołujące:IFS
początkowo był rozbrojony?set -u
(akaset -o nounset
)?IFS
utworzono opcję tylko do odczytudeclare -r IFS
?trap
obsługi)?Proszę nie zapisywać / przywracać IFS. Zamiast tego trzymaj się tymczasowych modyfikacji:
Aby ograniczyć modyfikację zmiennej do pojedynczego polecenia, wbudowanego lub wywołania funkcji, użyj
IFS="value" command
.Aby odczytać wiele zmiennych, dzieląc na określony znak (
:
użyty poniżej jako przykład), użyj:Aby odczytać tablicę, użyj (zrób to zamiast
array_var=( $str )
):Ogranicz efekty modyfikacji zmiennej do podpowłoki.
Aby wyprowadzić elementy tablicy oddzielone przecinkiem:
Aby przechwycić to w ciągu:
źródło
Najprostszym rozwiązaniem jest pobranie kopii oryginału
$IFS
, jak np. Odpowiedź msw. Jednak to rozwiązanie nie rozróżnia rozbrojeniaIFS
odIFS
zestawu równego pustemu ciągowi, co jest ważne w wielu aplikacjach. Oto bardziej ogólne rozwiązanie, które ujmuje to rozróżnienie:źródło