Dividi la stringa per delimitatore e ottieni l'elemento N-esimo


77

Ho una stringa:

one_two_three_four_five

Ho bisogno di salvare in una variabile Adel valore twoed in variabili Bil valore fourdalla stringa di cui sopra

Risposte:


109

Utilizzare cutcon _come delimitatore di campo e ottenere i campi desiderati:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

Puoi anche usare echoe pipe invece della stringa Here:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Esempio:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

C'è qualche alternativa? Sto usando ksh (non bsh) e restituisce ksh: errore di sintassi: `<'imprevisto
Alex

@Alex Controlla le mie modifiche.
Hememl

Bella risposta, ho una piccola domanda: cosa succede se la tua variabile "$ s" è una cartella di percorso. Quando provo a tagliare una cartella di percorsi mi piace seguire: `$ FILE = my_user / my_folder / [file] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Sai cosa sta succedendo qui?
Henry Navarro,

1
E se vuoi solo l'ultimo campo, usando solo i builtin della shell - senza bisogno di specificarne la posizione, o quando non conosci il numero di campi:echo "${s##*_}"
Amit Naidu,

19

Utilizzando solo costrutti sh POSIX, è possibile utilizzare costrutti di sostituzione dei parametri per analizzare un delimitatore alla volta. Si noti che questo codice presuppone che vi sia il numero richiesto di campi, altrimenti l'ultimo campo viene ripetuto.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

In alternativa, è possibile utilizzare una sostituzione dei parametri non quotata con l' espansione jolly disabilitata e IFSimpostata sul carattere delimitatore (funziona solo se il delimitatore è un singolo carattere non di spazi bianchi o se una sequenza di spazi vuoti è un delimitatore).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Questo blocca i parametri posizionali. Se lo fai in una funzione, sono interessati solo i parametri posizionali della funzione.

Ancora un altro approccio è usare il readbuiltin.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

L'uso di unset IFSnon torna IFSai valori predefiniti. Se dopo quello qualcuno OldIFS="$IFS"avrà un valore nullo all'interno di OldIFS. Inoltre, si presume che il valore precedente di IFS sia il valore predefinito, che è molto possibile (e utile) non essere. L'unica soluzione corretta è quella di archiviare old="$IFS"e ripristinare successivamente con IFS = "$ old". Oppure ... usa una sotto-shell (...). O, meglio ancora, leggi la mia risposta.
sorontar,

@sorontar unset IFSnon ripristina IFSil valore predefinito, ma restituisce la divisione del campo con l'effetto predefinito. Sì, è una limitazione, ma di solito accettabile in pratica. Il problema con una subshell è che dobbiamo estrarne i dati. Mostro una soluzione che non cambia lo stato alla fine, con read. (Funziona nelle shell POSIX, ma IIRC non nella shell Bourne perché eseguirà la readin una shell secondaria a causa del documento qui.) Usare <<<come nella risposta è una variante che funziona solo in ksh / bash / zsh.
Gilles 'SO- smetti di essere malvagio' il

Non vedo alcun problema anche con att o cimelio di una subshell. Tutte le shell testate (compresa la vecchia bourne) forniscono il valore corretto nella shell principale.
sorontar,

Cosa succede se il mio percorso è simile user/my_folder/[this_is_my_file]*? Quello che ottengo quando seguo questi passaggi è[this_is_my_file]*
Henry Navarro il

@HenryNavarro Questo output non corrisponde a nessuno degli snippet di codice nella mia risposta. Nessuno di loro fa nulla di speciale /.
Gilles 'SO- smetti di essere malvagio' il

17

Volevo vedere una awkrisposta, quindi eccone una:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

1
E se vuoi l'ultimo pezzo - senza bisogno di specificare la sua posizione o quando non conosci il numero di campi:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu,

8

Il modo più semplice (per shell con <<<) è:

 IFS='_' read -r a second a fourth a <<<"$string"

Usare una variabile temporale $ainvece che $_perché una shell si lamenta.

In uno script completo:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

Nessuna modifica IFS, non problemi con set -f(espansione del nome percorso) Nessuna modifica ai parametri posizionali ("$ @").


Per una soluzione portatile per tutte le shell (sì, inclusi tutti i POSIX) senza cambiare IFS o set -f, utilizzare l'equivalente ereditario (un po 'più complesso):

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Comprendi che queste soluzioni (sia il documento qui sia l'uso di <<<rimuoveranno tutte le nuove righe finali.
E che questo è progettato per un contenuto variabile "one liner". Le
soluzioni per multi-liners sono possibili ma richiedono costrutti più complessi.


Una soluzione molto semplice è possibile nella versione 4.4 di bash

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

Non esiste un equivalente per le shell POSIX, poiché molte shell POSIX non hanno array.

Per le shell che hanno array può essere semplice come:
(testato lavorando in attsh, lksh, mksh, ksh e bash)

set -f; IFS=_; arr=($string)

Ma con un sacco di idraulica aggiuntiva per mantenere e ripristinare variabili e opzioni:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

In zsh, le matrici iniziano in 1 e non dividono la stringa per impostazione predefinita.
Quindi alcune modifiche devono essere fatte per farlo funzionare in zsh.


le soluzioni che usano read sono semplici fintanto che OP non vuole estrarre il 76 ° e il 127 ° elemento da una lunga stringa ...
don_crissti,

@don_crissti Beh, certo, ma un costrutto simile: readarraypotrebbe essere più facile da usare per quella situazione.
sorontar,

@don_crissti Ho anche aggiunto una soluzione array per shell che hanno array. Per le shell POSIX, beh, non avendo array, i parametri posizionali fino a 127 elementi non sono una soluzione "semplice" da nessuna misura.
sorontar,

2

Con zshte puoi dividere la stringa (on _) in un array:

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

e quindi accedere a ciascun / qualsiasi elemento tramite l'indice dell'array:

print -r ${elements[4]}

Tieni presente che in zsh(diversamente da ksh/ bash) gli indici di array iniziano da 1 .


Ricorda di aggiungere un set -favviso alla prima soluzione. ... asterischi *forse?
sorontar,

@sorontar - perché pensi che io abbia bisogno set -f? Non sto usando read/ IFS. Prova le mie soluzioni con una stringa simile *_*_*o qualsiasi altra cosa ...
don_crissti

Non per zsh, ma l'utente ha chiesto una soluzione ksh, quindi potrebbe provare a usarla in quella shell. Un avvertimento lo aiuterà a evitare il problema.
sorontar,

1

È consentita una soluzione Python?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four

No. risposta negativa negativa
Raj Kumar

0

Un altro esempio imbarazzante; più semplice da capire.

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Può essere utilizzato anche con variabili.
Supponiamo:
this_str = "one_two_three_four_five"
Quindi le seguenti operazioni:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' `
B =` echo $ {this_str} | awk -F_ '{print $ 2}' `
C =` echo $ {this_str} | awk -F_ '{print $ 3}' '
... e così via ...

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.