Prosta odpowiedź brzmi: zwinąć wszystkie ograniczniki do jednego (pierwszego).
Które wymagają pętli (która działa krócej niż log(N)
razy):
var=':a bc::d ef:#$%_+$$% ^%&*(*&*^
$#,.::ghi::*::' # a long test string.
d=':@!#$%^&*()_+,.' # delimiter set
f=${d:0:1} # first delimiter
v=${var//["$d"]/"$f"}; # convert all delimiters to
: # the first of the delimiter set.
tmp=$v # temporal variable (v).
while
tmp=${tmp//["$f"]["$f"]/"$f"}; # collapse each two delimiters to one
[[ "$tmp" != "$v" ]]; # If there was a change
do
v=$tmp; # actualize the value of the string.
done
Pozostaje tylko poprawnie podzielić ciąg na jednym separatorze i wydrukować go:
readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
printf '<%s>' "${arr[@]}" ; echo
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):
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
I wydrukuj to:
printf '<%s>' "$@" ; echo
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śli command
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 polecenia command
sprawia, że uruchomienie kodu na bardziej muszli. Ale: usunięcie command
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 bez command
.
Wieloznakowy IFS
Tak, IFS może mieć wiele znaków, ale każdy znak wygeneruje jeden argument:
set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
printf '<%s>' "$@" ; echo
Wyjdzie:
<><a bc><><d ef><><><><><><><><>< ><><><><><><><><><
><><><><><><ghi><><><><><>
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 polecenie command
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
:
var=${var// /$'\3'} # protect spaces
var=${var//["$d"]/ } # convert all delimiters to spaces
set -f; # avoid expanding globs.
IFS=" " command eval set -- '""$var""' # split on spaces.
set -- "${@//$'\3'/ }" # convert spaces back.
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.