Come dividere una stringa in più stringhe separate da almeno uno spazio nella shell bash?


224

Ho una stringa contenente molte parole con almeno uno spazio tra le due. Come posso dividere la stringa in singole parole in modo da poterle scorrere tra loro?

La stringa viene passata come argomento. Es ${2} == "cat cat file". Come posso farcela?

Inoltre, come posso verificare se una stringa contiene spazi?


1
Che tipo di conchiglia? Bash, cmd.exe, powershell ...?
Alexey Sviridov,

Hai solo bisogno di eseguire il loop (ad es. Eseguire un comando per ciascuna delle parole)? O hai bisogno di memorizzare un elenco di parole per un uso successivo?
DVK,

Risposte:


281

Hai provato a passare la variabile stringa a un forciclo? Bash, per esempio, si dividerà automaticamente sugli spazi bianchi.

sentence="This is   a sentence."
for word in $sentence
do
    echo $word
done

 

This
is
a
sentence.

1
@MobRule - l'unico inconveniente di questo è che non puoi facilmente catturare (almeno non ricordo un modo) l'output per ulteriori elaborazioni. Vedi la mia soluzione "tr" qui sotto per qualcosa che invia roba a STDOUT
DVK

4
Si potrebbe semplicemente aggiungere ad una variabile: A=${A}${word}).
Lucas Jones,

1
imposta $ text [questo metterà le parole in $ 1, $ 2, $ 3 ... ecc.]
Rajesh

32
In realtà questo trucco non è solo una soluzione sbagliata, ma è anche estremamente pericoloso a causa del gorgogliamento delle coperture. touch NOPE; var='* a *'; for a in $var; do echo "[$a]"; doneoutput [NOPE] [a] [NOPE]invece del previsto [*] [a] [*](LF sostituiti da SPC per leggibilità).
Tino,

@mob cosa devo fare se voglio dividere la stringa in base a una stringa specifica? esempio ".xlsx" separatore.

296

Mi piace la conversione in un array, per poter accedere a singoli elementi:

sentence="this is a story"
stringarray=($sentence)

ora puoi accedere direttamente ai singoli elementi (inizia con 0):

echo ${stringarray[0]}

o converti nuovamente in stringa per eseguire il loop:

for i in "${stringarray[@]}"
do
  :
  # do whatever on $i
done

Ovviamente è stato risposto prima al looping diretto della stringa, ma quella risposta ha avuto lo svantaggio di non tenere traccia dei singoli elementi per un uso successivo:

for i in $sentence
do
  :
  # do whatever on $i
done

Vedi anche Bash Array Reference .


26
Purtroppo non del tutto perfetto, a causa del guscio della palla: touch NOPE; var='* a *'; arr=($var); set | grep ^arr=uscite arr=([0]="NOPE" [1]="a" [2]="NOPE")invece del previstoarr=([0]="*" [1]="a" [2]="*")
Tino,

@Tino: se non si desidera interferire con i globbing, è sufficiente disattivarlo. La soluzione funzionerà quindi anche con i caratteri jolly. Secondo me è l'approccio migliore.
Alexandros,

3
@Alexandros Il mio approccio è usare solo schemi, che sono sicuri di default e funzionano perfettamente in ogni contesto. Un requisito per cambiare lo shell-globbing per ottenere una soluzione sicura è molto più di un percorso molto pericoloso, è già il lato oscuro. Quindi il mio consiglio è di non abituarmi mai ad usare un modello come questo qui, perché prima o poi ti dimenticherai di alcuni dettagli, e quindi qualcuno sfrutta il tuo bug. Puoi trovare prove di tali exploit sulla stampa. Ogni. Singolo. Giorno.
Tino,

86

Basta usare le shell "set" integrate. Per esempio,

imposta $ testo

Dopodiché, le singole parole in $ text saranno in $ 1, $ 2, $ 3, ecc. Per robustezza, di solito si fa

set - junk $ text
cambio

per gestire il caso in cui $ text è vuoto o iniziare con un trattino. Per esempio:

text = "Questo è un test"
set - junk $ text
cambio
per parola; fare
  echo "[$ word]"
fatto

Questo stampa

[Questo]
[è]
[un]
[test]

5
Questo è un modo eccellente per dividere il var in modo che sia possibile accedere direttamente alle singole parti. +1; risolto il mio problema
Cheekysoft,

Stavo per suggerire di usare awkma setè molto più semplice. Ora sono un setfanboy. Grazie @Idelic!
Yzmir Ramirez,

22
Si prega di essere consapevoli del gorgoglio delle shell se si fanno queste cose: touch NOPE; var='* a *'; set -- $var; for a; do echo "[$a]"; doneoutput [NOPE] [a] [NOPE]invece del previsto [*] [a] [*]. Usalo solo se sei sicuro al 101% che non ci siano metacaratteri SHELL nella stringa divisa!
Tino,

4
@Tino: tale problema si applica ovunque, non solo qui, ma in questo caso è possibile disabilitare il globbing set -fprima set -- $vare set +fdopo.
Idelic

3
@Idelic: buona cattura. Anche con la set -ftua soluzione è sicuro. Ma set +fè il default di ogni shell, quindi è un dettaglio essenziale, che deve essere notato, perché altri probabilmente non ne sono consapevoli (come lo ero anche io).
Tino,

81

Il modo probabilmente più semplice e sicuro in BASH 3 e versioni successive è:

var="string    to  split"
read -ra arr <<<"$var"

(dove si arrtrova l'array che accetta le parti divise della stringa) o, se potrebbero esserci newline nell'input e si desidera più della semplice prima riga:

var="string    to  split"
read -ra arr -d '' <<<"$var"

(si prega di notare lo spazio in -d '', non può essere lasciato da parte), ma questo potrebbe darvi un inaspettato newline da <<<"$var"(poiché ciò aggiunge implicitamente un LF alla fine).

Esempio:

touch NOPE
var="* a  *"
read -ra arr <<<"$var"
for a in "${arr[@]}"; do echo "[$a]"; done

Emette il previsto

[*]
[a]
[*]

poiché questa soluzione (contrariamente a tutte le soluzioni precedenti qui) non è soggetta a globbing inaspettato e spesso incontrollabile delle coperture.

Anche questo ti dà tutta la potenza di IFS come probabilmente desideri:

Esempio:

IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
for a in "${arr[@]}"; do echo "[$a]"; done

Emette qualcosa del tipo:

[tino]
[x]
[1000]
[1000]
[Valentin Hilbig]
[/home/tino]
[/bin/bash]

Come puoi vedere, anche gli spazi possono essere preservati in questo modo:

IFS=: read -ra arr <<<' split  :   this    '
for a in "${arr[@]}"; do echo "[$a]"; done

uscite

[ split  ]
[   this    ]

Si noti che la gestione di IFSin BASH è un argomento a sé stante, quindi fare i test, alcuni argomenti interessanti su questo:

  • unset IFS: Ignora le serie di SPC, TAB, NL e l'inizio e la fine della linea
  • IFS='': Nessuna separazione dei campi, legge solo tutto
  • IFS=' ': Esecuzioni di SPC (e solo SPC)

Qualche ultimo esempio

var=$'\n\nthis is\n\n\na test\n\n'
IFS=$'\n' read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

uscite

1 [this is]
2 [a test]

mentre

unset IFS
var=$'\n\nthis is\n\n\na test\n\n'
read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

uscite

1 [this]
2 [is]
3 [a]
4 [test]

BTW:

  • Se non sei abituato ad $'ANSI-ESCAPED-STRING'abituarti, è un risparmio di tempo.

  • Se non includi -r(come in read -a arr <<<"$var"), allora leggi fa backslash escape. Questo è lasciato come esercizio per il lettore.


Per la seconda domanda:

Per testare qualcosa in una stringa di solito mi attengo case, poiché questo può controllare più casi contemporaneamente (nota: case esegue solo la prima corrispondenza, se hai bisogno di fallthrough usa caseistruzioni multiplce ), e questa necessità è abbastanza spesso il caso (gioco di parole destinato):

case "$var" in
'')                empty_var;;                # variable is empty
*' '*)             have_space "$var";;        # have SPC
*[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
*[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
*[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
*)                 default_case "$var";;      # if all above does not match
esac

Quindi puoi impostare il valore di ritorno per verificare SPC in questo modo:

case "$var" in (*' '*) true;; (*) false;; esac

Perché case? Perché di solito è un po 'più leggibile delle sequenze regex e grazie ai metacaratteri Shell gestisce molto bene il 99% di tutte le esigenze.


2
Questa risposta merita più voti, a causa delle problematiche evidenti e della sua completezza
Brian Agnew,

@brian Grazie. Nota che puoi usare set -fo set -o noglobcambiare il globbing, in modo tale che i metacaratteri della shell non danneggino più in questo contesto. Ma non sono proprio un amico di ciò, poiché ciò lascia molto potere nella shell / è molto soggetto a errori nel cambiare avanti e indietro questa impostazione.
Tino,

2
Risposta meravigliosa, merita davvero più voti. Nota a margine sulla caduta del caso: puoi usarlo per ;&raggiungerlo. Non sono sicuro di quale versione di bash sia apparsa. Sono un utente 4.3
Sergiy Kolodyazhnyy

2
@Serg grazie per averlo notato, poiché non lo sapevo ancora! Quindi l'ho cercato, è apparso in Bash4 . ;&è la caduta forzata senza controllo del modello come in C. E c'è anche quello ;;&che continua a fare ulteriori controlli del modello. Così ;;è come if ..; then ..; else if ..ed ;;&è come if ..; then ..; fi; if .., dove ;&è m=false; if ..; then ..; m=:; fi; if $m || ..; then ..- non si smette mai di imparare (dagli altri);)
Tino

@Tino È assolutamente vero: l'apprendimento è un processo continuo. In effetti, non lo sapevo ;;&prima che tu commentassi: D Grazie, e che la conchiglia sia con te;)
Sergiy Kolodyazhnyy,

43
$ echo "This is   a sentence." | tr -s " " "\012"
This
is
a
sentence.

Per controllare gli spazi, usa grep:

$ echo "This is   a sentence." | grep " " > /dev/null
$ echo $?
0
$ echo "Thisisasentence." | grep " " > /dev/null     
$ echo $?
1

1
In BASH echo "X" |di solito può essere sostituito da <<<"X", in questo modo: grep -s " " <<<"This contains SPC". È possibile individuare la differenza se si fa qualcosa di simile echo X | read varal contrario read var <<< X. Solo quest'ultima importa la variabile varnella shell corrente, mentre per accedervi nella prima variante è necessario raggruppare in questo modo:echo X | { read var; handle "$var"; }
Tino

17

(A) Per dividere una frase nelle sue parole (spazio separato) puoi semplicemente usare l'IFS predefinito usando

array=( $string )


Esempio che esegue il frammento seguente

#!/bin/bash

sentence="this is the \"sentence\"   'you' want to split"
words=( $sentence )

len="${#words[@]}"
echo "words counted: $len"

printf "%s\n" "${words[@]}" ## print array

uscirà

words counted: 8
this
is
the
"sentence"
'you'
want
to
split

Come puoi vedere puoi usare anche virgolette singole o doppie senza alcun problema

Note:
- questa è sostanzialmente la stessa risposta del mob , ma in questo modo memorizzi l'array per qualsiasi ulteriore necessità. Se hai solo bisogno di un singolo ciclo, puoi usare la sua risposta, che è una riga più corta :)
- fai riferimento a questa domanda per metodi alternativi per dividere una stringa in base al delimitatore.


(B) Per verificare la presenza di un carattere in una stringa puoi anche usare una corrispondenza di espressioni regolari.
Esempio per verificare la presenza di un carattere spazio che è possibile utilizzare:

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi

Per suggerimento regex (B) un +1, ma -1 per soluzione errata (A) poiché si tratta di un errore soggetto a gorgogliamento della shell. ;)
Tino,

6

Per controllare gli spazi solo con bash:

[[ "$str" = "${str% *}" ]] && echo "no spaces" || echo "has spaces"

1
echo $WORDS | xargs -n1 echo

Questo genera ogni parola, puoi elaborare quell'elenco come ritieni opportuno in seguito.

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.