La semplice risposta è: comprimi tutti i delimitatori in uno (il primo).
Ciò richiede un ciclo (che funziona meno delle log(N)
volte):
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
Non resta che dividere correttamente la stringa su un delimitatore e stamparla:
readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
printf '<%s>' "${arr[@]}" ; echo
Non è necessario set -f
né modificare IFS.
Testato con spazi, newline e caratteri glob. Tutto il lavoro. Abbastanza lento (come dovrebbe essere previsto un ciclo di shell).
Ma solo per bash (bash 4.4+ a causa dell'opzione -d
di readarray).
sh
Una versione di shell non può usare un array, l'unico array disponibile sono i parametri posizionali.
L'utilizzo tr -s
è solo una riga (IFS non cambia nello script):
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
E stampalo:
printf '<%s>' "$@" ; echo
Ancora lento, ma non molto di più.
Il comando command
non è valido in Bourne.
In zsh, command
chiama solo comandi esterni e fa fallire eval se command
viene usato.
In ksh, anche con command
, il valore di IFS viene modificato nell'ambito globale.
E command
fa fallire la divisione nelle shell correlate a mksh (mksh, lksh, posh) La rimozione del comando command
fa eseguire il codice su più shell. Ma: la rimozione command
farà in modo che IFS mantenga il suo valore nella maggior parte delle shell (eval è un builtin speciale) tranne che in bash (senza modalità posix) e zsh in modalità predefinita (nessuna emulazione). Questo concetto non può essere fatto funzionare in zsh predefinito con o senza command
.
IFS a più caratteri
Sì, IFS potrebbe essere multi carattere, ma ogni personaggio genererà un argomento:
set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
printf '<%s>' "$@" ; echo
Uscita:
<><a bc><><d ef><><><><><><><><>< ><><><><><><><><><
><><><><><><ghi><><><><><>
Con bash, puoi omettere la command
parola se non nell'emulazione sh / POSIX. Il comando fallirà in ksh93 (IFS mantiene il valore modificato). In zsh il comando command
fa sì che zsh tenti di trovare eval
un comando esterno (che non trova) e non riesce.
Quello che succede è che gli unici caratteri IFS che vengono automaticamente compressi in un delimitatore sono gli spazi bianchi IFS.
Uno spazio in IFS comprime tutti gli spazi consecutivi in uno. Una scheda comprime tutte le schede. Uno spazio e una scheda comprimono le serie di spazi e / o schede in un delimitatore. Ripeti l'idea con newline.
Per comprimere diversi delimitatori è necessario un po 'di giocoleria.
Supponendo che ASCII 3 (0x03) non sia utilizzato nell'input 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.
La maggior parte dei commenti su ksh, zsh e bash (about command
e IFS) si applicano ancora qui.
Un valore di $'\0'
sarebbe meno probabile nell'input di testo, ma le variabili bash non possono contenere NUL ( 0x00
).
Non ci sono comandi interni in sh per eseguire le stesse operazioni di stringa, quindi tr è l'unica soluzione per gli script sh.