Zawsze jestem bardzo niezdecydowany, żeby się z $IFS
tym pogodzić, ponieważ jest to globalny problem.
Ale często sprawia, że ładowanie ciągów do tablicy bash jest przyjemne i zwięzłe, a dla skryptów bash trudno jest uzyskać zwięzłość.
Myślę więc, że może być lepsze niż nic, jeśli spróbuję „zapisać” początkową zawartość $IFS
innej zmiennej, a następnie przywrócę ją natychmiast po tym, jak skończę używać $IFS
czegoś.
Czy to jest praktyczne? A może jest to w zasadzie bezcelowe i powinienem po prostu bezpośrednio IFS
cofnąć się do tego, co trzeba, do jego kolejnych zastosowań?
bash
shell-script
Steven Lu
źródło
źródło
$' \t\n'
jeśli używasz bash.unset $IFS
po prostu nie zawsze przywraca go do wartości domyślnej.Odpowiedzi:
W razie potrzeby możesz zapisać i przypisać do IFS. Nie ma w tym nic złego. Często zdarza się, aby zapisać jego wartość do przywrócenia po tymczasowej, szybkiej modyfikacji, takiej jak przykład przypisania tablicy.
Jak @llua wspomina w swoim komentarzu do twojego pytania, po prostu rozbrojenie IFS przywróci domyślne zachowanie, równoważne przypisaniu spacji-tab-nowej linii.
Warto zastanowić się, w jaki sposób może być bardziej problematyczne nie jawnie ustawiać / wyłączać IFS, niż to robić.
Od wersji POSIX 2013, 2.5.3 Shell Variables :
Wywołana powłoka zgodna z POSIX może, ale nie musi, dziedziczyć IFS ze swojego środowiska. Z tego wynika:
"$*"
), ale który może działać pod powłoką inicjującą IFS ze środowiska, musi jawnie ustawić / rozbroić IFS, aby bronić się przed ingerencją w środowisko.Uwaga: Należy zrozumieć, że w tej dyskusji słowo „przywołane” ma szczególne znaczenie. Powłoka jest wywoływana tylko wtedy, gdy jest jawnie wywoływana przy użyciu jej nazwy (w tym
#!/path/to/shell
shebang). Podpowłoka - taka, która może być utworzona przez$(...)
lubcmd1 || cmd2 &
- nie jest wywoływaną powłoką, a jej IFS (wraz z większością środowiska wykonawczego) jest identyczny z nadrzędnym. Wywołana powłoka ustawia wartość$
swojego pid, a podpowłoki ją dziedziczą.To nie jest tylko pedantyczna wyprawa; w tej dziedzinie istnieje rzeczywista rozbieżność. Oto krótki skrypt, który testuje scenariusz przy użyciu kilku różnych powłok. Eksportuje zmodyfikowany IFS (ustawiony na
:
) do wywoływanej powłoki, która następnie drukuje domyślny IFS.IFS nie jest generalnie oznaczony do eksportu, ale jeśli tak, zwróć uwagę, jak bash, ksh93 i mksh ignorują środowisko
IFS=:
, podczas gdy dash i busybox honorują to.Niektóre informacje o wersji:
Mimo że bash, ksh93 i mksh nie inicjują IFS ze środowiska, reeksportują zmodyfikowany IFS.
Jeśli z jakiegokolwiek powodu musisz przenośnie przekazać IFS przez środowisko, nie możesz tego zrobić za pomocą samego IFS; musisz przypisać wartość do innej zmiennej i oznaczyć tę zmienną do eksportu. Dzieci będą wówczas musiały wyraźnie przypisać tę wartość do swojego IFS.
źródło
IFS
wartość w większości sytuacji, w których ma być używana, i dlatego często nie jest zbyt produktywna nawet próba „zachowania” pierwotnej wartości.read
s lub podwójne cudzysłowy$*
. Ta lista jest tuż nad moją głową, więc może nie być wyczerpująca (szczególnie jeśli weźmiemy pod uwagę rozszerzenia POSIX współczesnych powłok).Ogólnie dobrą praktyką jest przywracanie domyślnych warunków.
Jednak w tym przypadku nie tak bardzo.
Dlaczego?:
$' \t\n'
.unset IFS
powoduje, że działa tak, jakby był ustawiony na domyślny .Problemem jest także przechowywanie wartości IFS.
Jeśli oryginalny IFS został rozbrojony, kod
IFS="$OldIFS"
ustawi IFS na""
, a nie rozbroi go.Aby faktycznie zachować wartość IFS (nawet jeśli jest rozbrojona), użyj tego:
źródło
bash
,unset IFS
nie rozbroi IFS, jeśli został zadeklarowany lokalnie w kontekście nadrzędnym (kontekst funkcji), a nie w bieżącym kontekście.Masz rację, że wahasz się przed zrzucaniem globalnego świata. Nie obawiaj się, możliwe jest napisanie czystego działającego kodu bez modyfikowania rzeczywistego globalnego
IFS
lub wykonywania uciążliwego i podatnego na błędy tańca zapisu / przywracania.Możesz:
ustaw IFS dla pojedynczego wywołania:
lub
ustaw IFS w podpowłoce:
Przykłady
Aby uzyskać ciąg rozdzielany przecinkami z tablicy:
Uwaga:
-
służy wyłącznie do ochrony pustej tablicy przedset -u
podaniem wartości domyślnej, gdy nie jest ustawiona (w tym przypadku jest to pusty ciąg) .The
IFS
Modyfikacja dotyczy tylko wewnątrz podpowłoce wywoływanych przez$()
podstawienie poleceń . Wynika to z tego, że podpowłoki mają kopie zmiennych wywołującej powłoki i dlatego mogą odczytywać ich wartości, ale wszelkie modyfikacje wykonywane przez podpowłokę wpływają tylko na kopię podpowłoki, a nie na zmienną rodzica.Być może zastanawiasz się: dlaczego nie pominąć podpowłoki i po prostu zrobić to:
Nie ma tu wywołania polecenia, a ten wiersz jest interpretowany jako dwa kolejne niezależne przypisania zmiennych, tak jakby to było:
Na koniec wyjaśnijmy, dlaczego ten wariant nie działa:
echo
Polecenie rzeczywiście nazwać swojąIFS
zmienną do zestawu,
, aleecho
nie obchodzi lub wykorzystanieIFS
. Magia rozwijania"${array[*]}"
do łańcucha jest wykonywana przez samą (pod-) powłokę, zanimecho
jeszcze zostanie wywołana.Aby wczytać cały plik (który nie zawiera
NULL
bajtów) do jednej zmiennej o nazwieVAR
:Uwaga:
IFS=
jest taki sam jakIFS=""
iIFS=''
, z których wszystkie ustawiają IFS na pusty ciąg, który jest bardzo różny odunset IFS
: jeśliIFS
nie jest ustawiony, zachowanie wszystkich funkcji bash, które wewnętrznie używająIFS
jest dokładnie takie samo, jak gdybyIFS
miał domyślną wartość$' \t\n'
.Oprawa
IFS
pustego ciągu gwarantuje zachowanie początkowych i końcowych białych znaków.Polecenie
-d ''
lub-d ""
informuje, aby zatrzymać tylko bieżące wywołanie naNULL
bajcie zamiast zwykłej nowej linii.Aby podzielić
$PATH
wzdłuż:
ograniczników:Ten przykład jest wyłącznie ilustracyjny. W ogólnym przypadku, gdy dzielisz wzdłuż separatora, poszczególne pola mogą zawierać (separowaną wersję) tego separatora. Pomyśl o próbie wczytania wiersza
.csv
pliku, którego kolumny mogą same zawierać przecinki (znaki specjalne lub cudzysłowy). Powyższy fragment kodu nie będzie działał zgodnie z przeznaczeniem w takich przypadkach.To powiedziawszy, jest mało prawdopodobne, abyś spotkał w sobie takie
:
zawierające ścieżki$PATH
. Chociaż ścieżki UNIX / Linux mogą zawierać a:
, wydaje się , że bash i tak nie będzie w stanie obsłużyć takich ścieżek, jeśli spróbujesz je dodać do swoich$PATH
plików i zapisać w nich pliki wykonywalne, ponieważ nie ma kodu do analizy dwukropków : kod źródłowy bash 4.4 .Na koniec zauważ, że fragment dołącza końcowy znak nowej linii do ostatniego elementu wynikowej tablicy (jak wywołano @ StéphaneChazelas w teraz usuniętych komentarzach) i że jeśli wejście jest pustym ciągiem, wynikiem będzie pojedynczy element tablica, w której element będzie się składał z nowej linii (
$'\n'
).Motywacja
Podstawowe
old_IFS="${IFS}"; command; IFS="${old_IFS}"
podejście, które dotyka globalnego,IFS
będzie działać zgodnie z oczekiwaniami w przypadku najprostszych skryptów. Jednak natychmiast po dodaniu złożoności może łatwo się rozdzielić i spowodować subtelne problemy:command
jest to funkcja bash, która również modyfikuje globalnąIFS
(albo bezpośrednio, albo ukryta, wewnątrz jeszcze jednej funkcji, którą wywołuje) i robiąc to błędnie używa tej samejold_IFS
zmiennej globalnej do zapisywania / przywracania, otrzymujesz błąd.IFS
był nieuzbrojony, naiwne zapisywanie i przywracanie nie zadziała, a nawet spowoduje całkowite awarie, jeśli powszechnie używanaset -u
( błędnie) (akaset -o nounset
) opcja powłoki obowiązuje.help trap
). Jeśli ten kod również modyfikuje globalnyIFS
lub zakłada, że ma określoną wartość, możesz uzyskać subtelne błędy.Można opracować bardziej niezawodną sekwencję zapisywania / przywracania (taką jak ta zaproponowana w tej drugiej odpowiedzi, aby uniknąć niektórych lub wszystkich tych problemów. Jednak trzeba będzie powtarzać ten fragment głośnego kodu płyty kotłowej wszędzie tam, gdzie tymczasowo potrzebujesz niestandardowego
IFS
. zmniejsza czytelność kodu i łatwość konserwacji.Dodatkowe uwagi na temat skryptów podobnych do bibliotek
IFS
jest szczególnie niepokojący dla autorów bibliotek funkcji powłoki, którzy muszą zapewnić niezawodne działanie swojego kodu bez względu na stan globalny (IFS
niezawodne działanie , opcji powłoki, ...) narzuconych przez ich wywołujących, a także bez zakłócania tego stanu (wywołujący mogą polegać na nim, aby zawsze pozostawał statyczny).Pisząc kod biblioteki, nie możesz polegać na
IFS
żadnej konkretnej wartości (nawet domyślnej) lub nawet na ustawieniu w ogóle. Zamiast tego musisz jawnie ustawićIFS
dla każdego fragmentu kodu, którego zachowanie zależy odIFS
.Jeśli
IFS
jest jawnie ustawione na niezbędną wartość (nawet jeśli jest to wartość domyślna) w każdym wierszu kodu, w którym wartość ma znaczenie przy użyciu dowolnego z dwóch mechanizmów opisanych w tej odpowiedzi, odpowiednich do zlokalizowania efektu, wówczas kod jest zarówno niezależny od państwa globalnego i całkowicie nie dopuszcza do jego zatkania. Podejście to ma dodatkową zaletę polegającą na tym, że jest bardzo wyraźne dla osoby czytającej skrypt, która maIFS
znaczenie dla tego jednego polecenia / rozszerzenia przy minimalnym koszcie tekstu (w porównaniu z nawet najbardziej podstawowym zapisaniem / przywróceniem).Na jaki kod ma to jednak wpływ
IFS
?Na szczęście nie ma tak wielu scenariuszy, w których liczy się to
IFS
(zakładając, że zawsze podajesz swoje rozszerzenia ):"$*"
i"${array[*]}"
rozszerzeniaread
wbudowanego targetowania wielu zmiennych (read VAR1 VAR2 VAR3
) lub zmiennej tablicowej (read -a ARRAY_VAR_NAME
)read
kierowania na jedną zmienną, jeśli chodzi o wiodące / końcowe białe znaki lub znaki niebiałeIFS
.źródło
:
kiedy:
jest separator?:
jest poprawnym znakiem do użycia w nazwie pliku w większości systemów plików UNIX / Linux, więc jest całkowicie możliwe, aby katalog zawierał nazwę:
. Być może niektóre powłoki mają opcję ucieczki:
w PATH za pomocą czegoś podobnego\:
, a wtedy zobaczysz kolumny, które nie są rzeczywistymi ogranicznikami (Wygląda na to, że bash nie pozwala na takie ucieczkę. Funkcja niskiego poziomu używana podczas iteracji po$PATH
prostu szuka:
w ciąg C: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATH
przykład podziału:
.Po co ryzykować literówkę, ustawiając IFS,
$' \t\n'
gdy wszystko, co musisz zrobić, toAlternatywnie możesz wywołać podpowłokę, jeśli nie potrzebujesz żadnych zmiennych ustawionych / zmodyfikowanych w:
źródło
IFS
początkowo było rozbrojone.