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 -fné 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 -ddi 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 commandnon è valido in Bourne.
In zsh, commandchiama solo comandi esterni e fa fallire eval se commandviene usato.
In ksh, anche con command, il valore di IFS viene modificato nell'ambito globale.
E commandfa fallire la divisione nelle shell correlate a mksh (mksh, lksh, posh) La rimozione del comando commandfa eseguire il codice su più shell. Ma: la rimozione commandfarà 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 commandparola se non nell'emulazione sh / POSIX. Il comando fallirà in ksh93 (IFS mantiene il valore modificato). In zsh il comando commandfa sì che zsh tenti di trovare evalun 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 commande 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.