Analiza składni tablicy za pomocą IFS z wartościami spacji niebiałych tworzy puste elementy.
Nawet użycie tr -s
do zmniejszenia wielu delimów do jednego delim nie wystarczy.
Przykład może jaśniej wyjaśnić ten problem.
Czy istnieje sposób na osiągnięcie „normalnych” wyników poprzez udoskonalenie IFS (czy istnieje powiązane ustawienie zmieniające zachowanie IFS? .... tzn. Działanie tak samo jak domyślna biała spacja IFS.
var=" abc def ghi "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
# (This data won't "glob", but unless globbing
# is actually needed, turn if off, because
# unusual/unexpected combinations of data can glob!
# and they can do it in the most obscure ways...
# With IFS, "you're not in Kansas any more! :)
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
set +f # enable globbing
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"
Oto wynik
============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>
Odpowiedzi:
Aby usunąć wiele znaków spacji (kolejnych spacji), można użyć dwóch rozszerzeń parametrów (ciąg / tablica). Sztuczka polega na ustawieniu
IFS
zmiennej na pusty ciąg znaków dla rozszerzenia parametru tablicy.Jest to udokumentowane w
man bash
rozdziale Podział słów :źródło
IFS=' '
(tj. Biała spacja) zachowuje się tak samo. Uważam, że jest to mniej mylące niż jawny argument zerowy („” lub „”) zIFS
.Z
bash
strony man:Oznacza to, że białe znaki IFS (spacja, tabulator i nowa linia) nie są traktowane jak inne separatory. Jeśli chcesz uzyskać dokładnie to samo zachowanie z alternatywnym separatorem, możesz wykonać zamianę separatora za pomocą
tr
lubsed
:%#%#%#%#%
Rzeczą jest magiczną wartość zastąpić możliwe przestrzenie wewnątrz pola, oczekuje się, że jest „wyjątkowy” (lub bardzo unlinkely). Jeśli masz pewność, że w polach nie będzie już miejsca, po prostu upuść tę część).źródło
tr
przykłady, aby pokazać problem ... Chcę uniknąć wywołania systemowego, więc przyjrzę się opcji bash wykraczającej poza tę, o${var##:}
której wspomniałem w komentarzu do odpowiedzi Glena ... Poczekam chwilę ... może istnieje sposób na nakłonienie IFS, w przeciwnym razie pierwsza część twojej odpowiedzi była po ...IFS
jest takie samo we wszystkich powłokach typu Bourne'a, jest określone w POSIX .IFS
znaków jako łańcucha ogranicznika. Odpowiedzi na moje pytanie najlepiej udzielił odpowiedźjon_d
, ale odpowiedź @ nazada pokazuje sprytny sposób użyciaIFS
bez pętli i aplikacji narzędziowych.Ponieważ bash IFS nie zapewnia wewnętrznego sposobu traktowania kolejnych znaków delimitera jako pojedynczego separatora (dla separatorów spoza spacji), stworzyłem wersję all bash (w przeciwieństwie do używania połączenia zewnętrznego np. Tr, awk, sed )
Może obsługiwać IFS z wieloma znakami ..
Oto jego czas wykonania; wraz z podobnymi testami dla opcji
tr
iawk
pokazanymi na tej stronie pytań / odpowiedzi ... Testy są oparte na 10000 iteracjach po prostu zbudowania tablicy (bez I / O) ...Oto wynik
Oto skrypt
źródło
Możesz to zrobić również z gawk, ale to nie jest ładne:
wyjścia
źródło
$var
na${var##:}
... Naprawdę szukałem sposobu na ulepszenie samego IFS .. Chcę zrobić to bez zewnętrznego wywołania (mam wrażenie, że bash może to zrobić bardziej efektywnie niż jakikolwiek zewnętrzny może .. więc będę śledził tę ścieżkę) ... twoja metoda działa (+1) .... Jak dotąd w miarę modyfikowania danych wejściowych wolałbym wypróbować to za pomocą basha, niż awk lub tr (bash 1.276s
...call (awk) 0m32.210s
,,,call (tr) 0m32.178s
... Zrób to kilka razy i możesz pomyśleć, że bash jest powolny! ... Czy w tym przypadku awk jest łatwiejszy? ... nie, jeśli masz już fragment kodu :) ... opublikuję go później; Muszę już iść.var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
Prosta odpowiedź brzmi: zwinąć wszystkie ograniczniki do jednego (pierwszego).
Które wymagają pętli (która działa krócej niż
log(N)
razy):Pozostaje tylko poprawnie podzielić ciąg na jednym separatorze i wydrukować go:
Nie ma potrzeby
set -f
ani zmieniać IFS.Testowany ze spacjami, znakami nowej linii i znakami globu. Cała praca. Dość powolny (należy się spodziewać pętli powłoki).
Ale tylko dla bash (bash 4.4+ ze względu na opcję
-d
readrayray).sh
Wersja powłoki nie może używać tablicy, jedyną dostępną tablicą są parametry pozycyjne.
Użycie
tr -s
jest tylko jedną linią (IFS nie zmienia się w skrypcie):I wydrukuj to:
Wciąż powolne, ale niewiele więcej.
Polecenie
command
jest nieprawidłowe w Bourne.W zsh
command
wywołuje tylko polecenia zewnętrzne i powoduje, że eval nie powiedzie się, jeślicommand
zostanie użyte.W ksh nawet przy
command
wartości IFS zmienia się w zakresie globalnym.I
command
sprawia, że podział nie w mksh powiązanych muszli (mksh, lksh, posh) usunięcie poleceniacommand
sprawia, że uruchomienie kodu na bardziej muszli. Ale: usunięciecommand
spowoduje, że IFS zachowa swoją wartość w większości powłok (eval jest specjalną wbudowaną funkcją), z wyjątkiem bash (bez trybu posix) i zsh w domyślnym trybie (bez emulacji). Nie można sprawić, aby ta koncepcja działała w domyślnym Zsh zarówno z, jak i bezcommand
.Wieloznakowy IFS
Tak, IFS może mieć wiele znaków, ale każdy znak wygeneruje jeden argument:
Wyjdzie:
Dzięki bash możesz pominąć to
command
słowo, jeśli nie jest emulacji sh / POSIX. Polecenie zakończy się niepowodzeniem w ksh93 (IFS zachowuje zmienioną wartość). W zsh poleceniecommand
powoduje, że zsh próbuje znaleźćeval
jako polecenie zewnętrzne (którego nie znajduje) i kończy się niepowodzeniem.Dzieje się tak, ponieważ jedynymi znakami IFS, które są automatycznie zwinięte do jednego separatora, są białe znaki IFS.
Jedno pole w IFS zwinie wszystkie kolejne spacje do jednego. Jedna karta zwinie wszystkie karty. Jedna spacja i jedna karta zwiną sekwencje spacji i / lub tabulatorów do jednego separatora. Powtórz pomysł z nową linią.
Aby zwinąć kilka ograniczników, wymagana jest pewna żonglerka.
Zakładając, że ASCII 3 (0x03) nie jest używany na wejściu
var
:Większość komentarzy na temat ksh, zsh i bash (about
command
i IFS) nadal obowiązuje tutaj.Wartość
$'\0'
mniej prawdopodobna przy wprowadzaniu tekstu, ale zmienne bash nie mogą zawierać NULs (0x00
).W sh nie ma wewnętrznych poleceń do wykonania tych samych operacji na łańcuchach, więc tr jest jedynym rozwiązaniem dla skryptów sh.
źródło
command eval