C'è qualcosa come JavaScript "split ()" nella shell?


18

È molto facile da usare split()in JavaScript per suddividere una stringa in un array.

Che dire di shell script?

Di 'che voglio fare questo:

$ script.sh var1_var2_var3

Quando l'utente fornisce tale stringa var1_var2_var3a script.sh, all'interno dello script convertirà la stringa in un array come

array=( var1 var2 var3 )
for name in ${array[@]}; do
    # some code
done

1
cosa shellstai usando, con bashte puoi fareIFS='_' read -a array <<< "${string}"
gwillie

perlposso farlo anche io. Non è una shell "pura", ma è abbastanza comune.
Sobrique,

@Sobrique Sono anche ignaro della definizione tecnica di shell "pura", ma c'è node.js.
emory

Tendo a lavorare su 'è probabilmente installato sul mio Linux box di default' e non preoccuparsi delle minuzie :)
Sobrique,

Risposte:


24

Le shell tipo Bourne / POSIX hanno un operatore split + glob ed è invocato ogni volta che si lascia un parametro di espansione ( $var, $-...), sostituzione comando ( $(...)) o espansione aritmetica ( $((...))) non quotato nel contesto dell'elenco.

In realtà, l'hai invocato per errore quando lo hai fatto for name in ${array[@]}invece di for name in "${array[@]}". (In realtà, dovresti stare attento che invocare quell'operatore in quel modo per errore è fonte di molti bug e vulnerabilità di sicurezza ).

Quell'operatore è configurato con il $IFSparametro speciale (per dire su quali personaggi dividere (anche se attenzione che spazio, tab e newline ricevono un trattamento speciale lì)) e l' -fopzione per disabilitare ( set -f) o abilitare ( set +f) la globparte.

Si noti inoltre che mentre Sin $IFSera originariamente (nella shell Bourne da dove $IFSproviene) per Separatore, nelle shell POSIX, i caratteri in $IFSdovrebbero piuttosto essere visti come delimitatori o terminatori (vedere sotto per un esempio).

Quindi dividere su _:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
array=($string) # invoke the split+glob operator

for i in "${array[@]}"; do # loop over the array elements.

Per vedere la distinzione tra separatore e delimitatore , prova su:

string='var1_var2_'

Ciò lo dividerà in var1e var2solo (nessun elemento extra vuoto).

Quindi, per renderlo simile a JavaScript split(), avresti bisogno di un ulteriore passaggio:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
temp=${string}_ # add an extra delimiter
array=($temp) # invoke the split+glob operator

(nota che dividerebbe un elemento vuoto $stringin 1 (non 0 ), come JavaScript split().

Per vedere la scheda trattamenti speciali, spazio e newline ricevere, confronta:

IFS=' '; string=' var1  var2  '

(dove arrivi var1e var2) con

IFS='_'; string='_var1__var2__'

dove si ottiene: '', var1, '', var2, ''.

Si noti che la zshshell non invoca quell'operatore split + glob in modo implicito a meno che non sia presente sho kshemulato. Lì, devi invocarlo esplicitamente. $=stringper la parte divisa, $~stringper la parte glob ( $=~stringper entrambi) e ha anche un operatore di divisione in cui è possibile specificare il separatore:

array=(${(s:_:)string})

o per preservare gli elementi vuoti:

array=("${(@s:_:)string}")

Si noti che non sè per scissione , non delimitando (anche con $IFSuna nota POSIX non conformità di zsh). È diverso da quello di JavaScript split()in quanto una stringa vuota è suddivisa in elemento 0 (non 1).

Una notevole differenza con $IFS-splitting è che si ${(s:abc:)string}divide sulla abcstringa, mentre con IFS=abc, che si divide su a, bo c.

Con zshe ksh93, il trattamento speciale che spazio, tab o newline riceve può essere rimosso raddoppiandolo $IFS.

Come nota storica, la shell Bourne (l'antenato o le moderne shell POSIX) ha sempre messo a nudo gli elementi vuoti. Aveva anche una serie di bug relativi alla divisione e all'espansione di $ @ con valori non predefiniti di $IFS. Ad esempio IFS=_; set -f; set -- $@non sarebbe equivalente a IFS=_; set -f; set -- $1 $2 $3....

Frazionamento su regexps

Ora per qualcosa di più vicino a JavaScript split()che può dividere su espressioni regolari, dovresti fare affidamento su utility esterne.

Nel toolbox POSIX, awkha un splitoperatore che può dividere su espressioni regolari estese (che sono più o meno un sottoinsieme delle espressioni regolari simili al Perl supportate da JavaScript).

split() {
  awk -v q="'" '
    function quote(s) {
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN {
      n = split(ARGV[1], a, ARGV[2])
      for (i = 1; i <= n; i++) printf " %s", quote(a[i])
      exit
    }' "$@"
}
string=a__b_+c
eval "array=($(split "$string" '[_+]+'))"

La zshshell ha il supporto incorporato per le espressioni regolari compatibili con Perl (nel suo zsh/pcremodulo), ma usarlo per dividere una stringa, sebbene possibile sia relativamente ingombrante.


C'è qualche motivo per trattamenti speciali con tab, spazio e newline?
cuonglm,

1
@cuonglm, generalmente si desidera dividere ovvero quando i delimitatori sono vuoti, nel caso di delimitatori non vuoti (come per divisione $PATHsulla :) al contrario, in genere si desidera conservare elementi vuoti. Si noti che nella shell Bourne, tutti i personaggi stavano ricevendo il trattamento speciale, kshmodificato per far sì che solo quelli vuoti (solo spazio, tab e newline) fossero trattati in modo speciale.
Stéphane Chazelas,

Bene, la recente nota della shell Bourne aggiunta mi ha sorpreso. E per il completamento, dovresti aggiungere la nota per il zshtrattamento con stringa contenente 2 o più caratteri in ${(s:string:)var}? Se aggiunto, posso cancellare la mia risposta :)
cuonglm

1
Cosa intendi con "Nota anche che la S in $ IFS è per Delimitatore, non per Separatore"? Capisco la meccanica e che ignora i separatori finali ma le Sacronimo di Separatore , non delimitatore . Almeno, questo è quello che dice il mio manuale di Bash.
terdon

@terdon, $IFSproviene dalla shell Bourne in cui era separatore , ksh ha cambiato il comportamento senza cambiare il nome. Lo dico per sottolineare che split+glob(tranne in zsh o pdksh) non si divide semplicemente più.
Stéphane Chazelas,

7

Sì, utilizzalo IFSe impostalo su _. Quindi utilizzare read -aper archiviare in un array ( -rdisattiva l'espansione della barra rovesciata). Nota che questo è specifico per bash; ksh e zsh hanno caratteristiche simili con una sintassi leggermente diversa e plain sh non ha affatto variabili di array.

$ r="var1_var2_var3"
$ IFS='_' read -r -a array <<< "$r"
$ for name in "${array[@]}"; do echo "+ $name"; done
+ var1
+ var2
+ var3

Da man bash:

leggere

-un aname

Le parole sono assegnate agli indici sequenziali dell'aname della variabile array, a partire da 0. aname non è impostato prima che vengano assegnati nuovi valori. Gli altri argomenti relativi al nome vengono ignorati.

IFS

Il separatore di campo interno che viene utilizzato per la divisione delle parole dopo l'espansione e per dividere le linee in parole con il comando incorporato read. Il valore predefinito è `` ''.

Si noti che si readferma alla prima riga. Passa -d ''per readevitarlo, ma in tal caso, ci sarà una nuova riga aggiuntiva alla fine dovuta <<<all'operatore. Puoi rimuoverlo manualmente:

IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}

Ciò presuppone $rche non contenga caratteri di nuova riga o barre rovesciate. Si noti inoltre che funzionerà solo nelle versioni recenti della bashshell.
Stéphane Chazelas,

@ StéphaneChazelas buon punto. Sì, questo è il caso "base" di una stringa. Per il resto, tutti dovrebbero cercare una risposta esaustiva. Per quanto riguarda le versioni di bash, è read -astato introdotto in bash 4, giusto?
fedorqui,

1
scusatemi, pensavo <<<fosse stato aggiunto solo di recente, bashma sembra che sia stato lì dal 2.05b (2002). read -aè anche più vecchio di così. <<<viene da zshed è supportato da ksh93(e mksh e yash) ma read -aè specifico per bash (è -Ain ksh93, yash e zsh).
Stéphane Chazelas,

@ StéphaneChazelas c'è un modo "semplice" per trovare quando si sono verificati questi cambiamenti? Dico "facile" di non scavare nei file di rilascio, forse una pagina che li mostra tutti.
fedorqui,

1
Guardo i log delle modifiche per questo. zsh ha anche un repository git con cronologia risalente alla 3.1.5 e la sua mailing list viene utilizzata anche per tenere traccia delle modifiche.
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.