In Bash, come posso verificare se una stringa inizia con un valore?


745

Vorrei verificare se una stringa inizia con "nodo", ad esempio "nodo001". Qualcosa di simile a

if [ $HOST == user* ]
  then
  echo yes
fi

Come posso farlo correttamente?


Devo inoltre combinare le espressioni per verificare se HOST è "user1" o inizia con "node"

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

Come posso farlo correttamente?


7
Non essere troppo tentato di combinare espressioni. Può sembrare più brutto avere due condizionali separati, sebbene sia possibile fornire messaggi di errore migliori e semplificare il debug dello script. Inoltre eviterei le funzionalità bash. L'interruttore è la strada da percorrere.
hendry,

Risposte:


1073

Questo frammento della Guida allo scripting Advanced Bash dice:

# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.

[[ $a == z* ]]   # True if $a starts with a "z" (wildcard matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

Quindi l'hai quasi corretto; avevi bisogno di doppie parentesi, non singole.


Per quanto riguarda la tua seconda domanda, puoi scriverla in questo modo:

HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes2
fi

Quale risuonerà

yes1
yes2

ifÈ difficile abituarsi alla sintassi di Bash (IMO).


12
Per regex intendi [[$ a = ~ ^ z. *]]?
JStrahl,

3
Quindi c'è una differenza funzionale tra [[ $a == z* ]]e [[ $a == "z*" ]]? In altre parole: funzionano in modo diverso? E cosa intendi specificamente quando dici "$ a è uguale a z *"?
Niels Bom,

5
Non è necessario il separatore di istruzioni ";" se metti "allora" sulla sua stessa linea
Yaza,

6
Solo per completezza: per verificare se una stringa FINISCE con ...:[[ $a == *com ]]
lepe

4
L'ABS è una scelta sfortunata di riferimenti: è proprio il W3Schools di bash, pieno di contenuti obsoleti ed esempi di cattive pratiche; il canale #bash di freenode ha cercato di scoraggiarne l'uso almeno dal 2008 . Qualche possibilità di reimpostare su BashFAQ # 31 ? (Avrei anche suggerito la wiki di Bash-Hackers, ma è in calo da un po 'adesso).
Charles Duffy,

207

Se stai utilizzando una versione recente di Bash (v3 +), suggerisco l'operatore di confronto regex di Bash =~, ad esempio,

if [[ "$HOST" =~ ^user.* ]]; then
    echo "yes"
fi

Per abbinare this or thatin una regex, utilizzare |, ad esempio,

if [[ "$HOST" =~ ^user.*|^host1 ]]; then
    echo "yes"
fi

Nota: questa è la sintassi delle espressioni regolari "corretta".

  • user*significa usee zero o più occorrenze di r, quindi usee userrrrcorrisponderanno.
  • user.*significa usere zero o più occorrenze di qualsiasi personaggio, quindi user1, userXcorrisponderanno.
  • ^user.*significa abbinare il modello user.*all'inizio di $ HOST.

Se non si ha familiarità con la sintassi delle espressioni regolari, provare a fare riferimento a questa risorsa .


Grazie, Brabster! Ho aggiunto al post originale una nuova domanda su come combinare le espressioni in if cluase.
Tim

2
È un peccato che la risposta accettata non dica nulla sulla sintassi dell'espressione regolare.
CarlosRos,

20
Cordiali saluti, l' =~operatore Bash esegue la corrispondenza delle espressioni regolari solo quando il lato destro è UNQUOTED. Se si cita il lato destro "Qualsiasi parte del motivo può essere citata per forzare la corrispondenza come stringa." (1.) assicurati di mettere sempre le espressioni regolari a destra senza virgolette e (2.) se memorizzi l'espressione regolare in una variabile, assicurati di NON citare il lato destro quando esegui l'espansione dei parametri.
Trevor Boyd Smith,

145

Cerco sempre di rimanere con POSIX shinvece di usare le estensioni di Bash, poiché uno dei punti principali dello scripting è la portabilità (oltre a collegare i programmi, non sostituirli).

In sh, c'è un modo semplice per verificare una condizione "is-prefix".

case $HOST in node*)
    # Your code here
esac

Considerando quanti anni ha sh arcano e crufty (e Bash non è la cura: è più complicato, meno coerente e meno portatile), vorrei sottolineare un aspetto funzionale molto bello: mentre alcuni elementi di sintassi come casesono integrati , i costrutti risultanti non sono diversi da qualsiasi altro lavoro. Possono essere composti allo stesso modo:

if case $HOST in node*) true;; *) false;; esac; then
    # Your code here
fi

O ancora più breve

if case $HOST in node*) ;; *) false;; esac; then
    # Your code here
fi

O ancora più breve (solo per presentare !come un elemento del linguaggio - ma questo è cattivo stile ora)

if ! case $HOST in node*) false;; esac; then
    # Your code here
fi

Se ti piace essere esplicito, crea il tuo elemento linguistico:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

Non è abbastanza carino?

if beginswith node "$HOST"; then
    # Your code here
fi

E dato che shfondamentalmente sono solo lavori e liste di stringhe (e processi interni, fuori dai quali i lavori sono composti), ora possiamo anche fare una leggera programmazione funzionale:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }

all() {
    test=$1; shift
    for i in "$@"; do
        $test "$i" || return
    done
}

all "beginswith x" x xy xyz ; checkresult  # Prints TRUE
all "beginswith x" x xy abc ; checkresult  # Prints FALSE

Questo è elegante Non che io sostenga l'uso shper qualcosa di serio: si rompe troppo rapidamente sui requisiti del mondo reale (niente lambda, quindi dobbiamo usare le stringhe. Ma le chiamate di funzione di nidificazione con stringhe non sono possibili, i tubi non sono possibili, ecc.)


12
+1 Non solo è portatile, è anche leggibile, idiomatico ed elegante (per lo script shell). Si estende anche naturalmente a più schemi; case $HOST in user01 | node* ) ...
Tripleee

Esiste un nome per questo tipo di formattazione del codice? if case $HOST in node*) true;; *) false;; esac; then L'ho visto qua e là, ai miei occhi sembra un po 'stropicciato.
Niels Bom,

@NielsBom Non so esattamente cosa intendi per formattazione, ma il mio punto era che il codice della shell è molto componibile . I casecomandi Becaues sono comandi, possono andare dentro if ... then.
Jo So,

Non vedo nemmeno perché sia ​​compostabile, non capisco abbastanza script di shell per questo :-) La mia domanda è su come questo codice utilizza parentesi non corrispondenti e doppi punti e virgola. Non assomiglia affatto allo script della shell che ho visto prima, ma potrei essere abituato a vedere lo script bash più di quello sh, quindi potrebbe essere.
Niels Bom,

Nota: dovrebbe essere beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }altrimenti se $1ha un valore letterale *o ?potrebbe dare una risposta errata.
LLFourn

80

Puoi selezionare solo la parte della stringa che vuoi controllare:

if [ "${HOST:0:4}" = user ]

Per la tua domanda di follow-up, puoi usare un OR :

if [[ "$HOST" == user1 || "$HOST" == node* ]]

8
Dovresti raddoppiare la citazione${HOST:0:4}
Jo So,

@Jo So: Qual è il motivo?
Peter Mortensen,

@PeterMortensen, provaHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So

60

Preferisco gli altri metodi già pubblicati, ma ad alcune persone piace usare:

case "$HOST" in 
    user1|node*) 
            echo "yes";;
        *)
            echo "no";;
esac

Modificare:

Ho aggiunto i tuoi supplenti alla dichiarazione del caso sopra

Nella tua versione modificata hai troppe parentesi. Dovrebbe sembrare come questo:

if [[ $HOST == user1 || $HOST == node* ]];

Grazie Dennis! Ho aggiunto al post originale una nuova domanda su come combinare le espressioni in if cluase.
Tim

11
"ad alcune persone piace ...": questo è più portatile tra versioni e shell.
carlosayam,

Con le istruzioni case è possibile lasciare le virgolette attorno alla variabile poiché non si verifica alcuna divisione delle parole. So che è inutile e incoerente, ma mi piace lasciare le virgolette lì per renderlo localmente più visivamente accattivante.
Jo So,

E nel mio caso, ho dovuto lasciare le virgolette prima): "/ *") non ha funzionato, / *) ha funzionato. (Sto cercando stringhe che iniziano con /, cioè, percorsi assoluti)
Josiah Yoder

36

Mentre trovo la maggior parte delle risposte qui abbastanza corrette, molte di esse contengono Bashismi inutili. L'espansione dei parametri POSIX ti offre tutto ciò di cui hai bisogno:

[ "${host#user}" != "${host}" ]

e

[ "${host#node}" != "${host}" ]

${var#expr}strisce il più piccolo di corrispondenza del prefisso exprda ${var}e restituisce tale. Quindi se ${host}non non iniziare con user( node), ${host#user}( ${host#node}) è la stessa ${host}.

exprconsente i fnmatch()caratteri jolly, quindi ${host#node??}anche gli amici lavorano.


2
Direi che il bashismo [[ $host == user* ]]potrebbe essere necessario, dal momento che è molto più leggibile di [ "${host#user}" != "${host}" ]. In quanto tale, che si controlla l'ambiente in cui viene eseguito lo script (destinazione delle ultime versioni di bash), è preferibile il primo.
x-yuri,

2
@ x-yuri Francamente, lo metterei semplicemente in una has_prefix()funzione e non la guarderei mai più.
Dhke

30

Dato che #ha un significato in Bash, sono arrivato alla seguente soluzione.

Inoltre, mi piace meglio imballare le stringhe con "" per superare gli spazi, ecc.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi

Questo era esattamente ciò di cui avevo bisogno. Grazie!
Ionică Bizău,

grazie, mi chiedevo anche come abbinare una stringa a partire blah:, sembra che questa sia la risposta!
Anentropico,

12

Aggiungendo un po 'più dettagli di sintassi alla risposta di rango più alto di Mark Rushakoff.

L'espressione

$HOST == node*

Può anche essere scritto come

$HOST == "node"*

L'effetto è lo stesso. Assicurati solo che il carattere jolly sia esterno al testo tra virgolette. Se il carattere jolly è racchiuso tra virgolette, verrà interpretato letteralmente (ovvero non come un carattere jolly).


8

@OP, per entrambe le tue domande puoi usare case / esac:

string="node001"
case "$string" in
  node*) echo "found";;
  * ) echo "no node";;
esac

Seconda domanda

case "$HOST" in
 node*) echo "ok";;
 user) echo "ok";;
esac

case "$HOST" in
 node*|user) echo "ok";;
esac

O Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac

Nota che ;&è disponibile solo in Bash> = 4.
In pausa fino a nuovo avviso.

6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

non funziona, perché tutti [, [[e testriconoscere la stessa grammatica non ricorsiva. Vedi la sezione EXPRESSIONS CONDIZIONALI sulla tua pagina man di Bash.

A parte questo, dice SUSv3

Il comando condizionale derivato da KornShell (doppia parentesi [[]] ) è stato rimosso dalla descrizione del linguaggio dei comandi della shell in una proposta iniziale. Sono state sollevate obiezioni sul fatto che il vero problema sia l'uso improprio del comando test ( [ ) e metterlo nella shell è il modo sbagliato di risolvere il problema. Sono invece sufficienti la documentazione corretta e una nuova parola riservata alla shell ( ! ).

I test che richiedono più operazioni di test possono essere eseguiti a livello di shell utilizzando le singole invocazioni del comando test e dei logici della shell, anziché utilizzare il flag di test soggetto a errori -o .

Dovresti scriverlo in questo modo, ma test non lo supporta:

if [ $HOST == user1 -o $HOST == node* ];
then
echo yes
fi

test utilizza = per l'uguaglianza delle stringhe e, soprattutto, non supporta la corrispondenza dei modelli.

case/ esacha un buon supporto per la corrispondenza dei motivi:

case $HOST in
user1|node*) echo yes ;;
esac

Ha il vantaggio aggiuntivo di non dipendere da Bash e la sintassi è portatile. Dalla specifica Unix singola , The Shell Command Language :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac

1
[e testsono built-in di Bash e programmi esterni. Prova type -a [.
In pausa fino a nuovo avviso.

Mille grazie per aver spiegato i problemi con il "composto o", semplicemente qualcuno - stava cercando esattamente qualcosa del genere! Saluti! PS nota (indipendente OP): if [ -z $aa -or -z $bb ]; ... dà " bash: [: -o: operatore binario previsto "; comunque if [ -z "$aa" -o -z "$bb" ] ; ...passa.
sdaau,

2

grep

Dimenticando le prestazioni, questo è POSIX e sembra più bello delle casesoluzioni:

mystr="abcd"
if printf '%s' "$mystr" | grep -Eq '^ab'; then
  echo matches
fi

Spiegazione:


2

Ho modificato la risposta di @ markrushakoff per renderla una funzione richiamabile:

function yesNo {
  # Prompts user with $1, returns true if response starts with y or Y or is empty string
  read -e -p "
$1 [Y/n] " YN

  [[ "$YN" == y* || "$YN" == Y* || "$YN" == "" ]]
}

Usalo in questo modo:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] Y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] yes
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n]
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] ddddd
false

Ecco una versione più complessa che prevede un valore predefinito specificato:

function toLowerCase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function yesNo {
  # $1: user prompt
  # $2: default value (assumed to be Y if not specified)
  # Prompts user with $1, using default value of $2, returns true if response starts with y or Y or is empty string

  local DEFAULT=yes
  if [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fi
  if [[ "$DEFAULT" == y* ]]; then
    local PROMPT="[Y/n]"
  else
    local PROMPT="[y/N]"
  fi
  read -e -p "
$1 $PROMPT " YN

  YN="$( toLowerCase "$YN" )"
  { [ "$YN" == "" ] && [[ "$PROMPT" = *Y* ]]; } || [[ "$YN" = y* ]]
}

Usalo in questo modo:

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N]
false

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N] y
true

$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

-5

Un'altra cosa che puoi fare è catscoprire cosa stai facendo eco e convogliareinline cut -c 1-1

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.