Wie kann ich in Bash überprüfen, ob eine Zeichenfolge mit einem bestimmten Wert beginnt?

745

Ich möchte überprüfen, ob eine Zeichenfolge mit "node" beginnt, z. B. "node001". Etwas wie

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

Wie kann ich das richtig machen?


Ich muss außerdem Ausdrücke kombinieren, um zu überprüfen, ob HOST entweder "user1" ist oder mit "node" beginnt.

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

> > > -bash: [: too many arguments

Wie kann ich das richtig machen?

Tim
quelle
7
Seien Sie nicht zu versucht, Ausdrücke zu kombinieren. Es mag hässlicher aussehen, zwei separate Bedingungen zu haben, obwohl Sie bessere Fehlermeldungen geben und das Debuggen Ihres Skripts vereinfachen können. Auch würde ich die Bash-Features vermeiden. Der Schalter ist der richtige Weg.
Henry

Antworten:

1073

In diesem Snippet im Advanced Bash Scripting Guide heißt es:

# 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).

Sie hatten es also fast richtig; Sie brauchten doppelte Klammern, keine einfachen Klammern.


In Bezug auf Ihre zweite Frage können Sie sie folgendermaßen schreiben:

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

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

Welches wird widerhallen

yes1
yes2

Bashs ifSyntax ist schwer zu gewöhnen (IMO).

Mark Rushakoff
quelle
12
Für Regex meinst du [[$ a = ~ ^ z. *]]?
JStrahl
3
Gibt es also einen funktionalen Unterschied zwischen [[ $a == z* ]]und [[ $a == "z*" ]]? Mit anderen Worten: Arbeiten sie anders? Und was genau meinst du, wenn du sagst "$ a ist gleich z *"?
Niels Bom
5
Sie brauchen das Anweisungstrennzeichen ";" nicht. Wenn Sie "dann" in eine eigene Zeile setzen
Yaza
6
Nur der Vollständigkeit halber: Um zu überprüfen, ob eine Zeichenfolge mit ... [[ $a == *com ]]
endet
4
Das ABS ist eine unglückliche Wahl von Referenzen - es ist sehr viel die W3Schools von Bash, voll von veralteten Inhalten und Beispielen für schlechte Praktiken; Der Freenode # Bash-Kanal versucht zumindest seit 2008, von seiner Verwendung abzuraten . Gibt es eine Chance auf einen erneuten Termin bei BashFAQ # 31 ? (Ich hätte auch das Bash-Hackers-Wiki vorgeschlagen, aber es ist jetzt schon ein bisschen ausgefallen).
Charles Duffy
207

Wenn Sie eine neuere Version von Bash (v3 +) verwenden, empfehle ich beispielsweise den Bash-Regex-Vergleichsoperator =~.

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

this or thatVerwenden Sie |zum Beispiel " Regex", um eine Übereinstimmung zu erzielen

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

Hinweis - Dies ist die richtige Syntax für reguläre Ausdrücke.

  • user*bedeutet useund null oder mehr Vorkommen von r, so useund userrrrwerden übereinstimmen.
  • user.*Mittel userund Null-oder-mehr beliebigen Zeichen, so user1, userXwerden entsprechen.
  • ^user.*bedeutet, dass das Muster user.*am Anfang von $ HOST übereinstimmt .

Wenn Sie mit der Syntax regulärer Ausdrücke nicht vertraut sind, verweisen Sie auf diese Ressource .

Brabster
quelle
Danke, Brabster! Ich habe dem ursprünglichen Beitrag eine neue Frage hinzugefügt, wie Ausdrücke in if cluase kombiniert werden können.
Tim
2
Schade, dass die akzeptierte Antwort nichts über die Syntax des regulären Ausdrucks aussagt.
CarlosRos
20
Zu =~Ihrer Information, der Bash- Operator führt nur dann einen Abgleich regulärer Ausdrücke durch, wenn die rechte Seite UNQUOTED ist. Wenn Sie die rechte Seite zitieren "Jeder Teil des Musters kann zitiert werden, um zu erzwingen, dass es als Zeichenfolge abgeglichen wird." (1.) Stellen Sie sicher, dass die regulären Ausdrücke immer ohne Anführungszeichen rechts stehen. (2.) Wenn Sie Ihren regulären Ausdruck in einer Variablen speichern, stellen Sie sicher, dass Sie bei der Parametererweiterung NICHT die rechte Seite in Anführungszeichen setzen.
Trevor Boyd Smith
145

Ich versuche immer, bei POSIX zu bleiben, shanstatt Bash-Erweiterungen zu verwenden, da einer der Hauptpunkte der Skripterstellung die Portabilität ist (neben dem Verbinden von Programmen, nicht deren Ersetzen).

Im sh gibt es eine einfache Möglichkeit, nach einer "is-prefix" -Bedingung zu suchen.

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

In Anbetracht dessen, wie alt, arkan und crufty sh ist (und Bash ist nicht das Heilmittel: Es ist komplizierter, weniger konsistent und weniger portabel), möchte ich auf einen sehr schönen funktionalen Aspekt hinweisen: Während einige Syntaxelemente wie case eingebaut sind Die resultierenden Konstrukte unterscheiden sich nicht von anderen Jobs. Sie können auf die gleiche Weise zusammengesetzt werden:

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

Oder noch kürzer

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

Oder noch kürzer (nur um !als Sprachelement zu präsentieren - aber das ist jetzt ein schlechter Stil)

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

Wenn Sie explizit sein möchten, erstellen Sie Ihr eigenes Sprachelement:

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

Ist das nicht eigentlich ganz nett?

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

Und seit sh es sich im Grunde nur um Jobs und String-Listen handelt (und um interne Prozesse, aus denen Jobs bestehen), können wir jetzt sogar eine leichte funktionale Programmierung durchführen:

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

Das ist elegant. Nicht, dass ich die Verwendung shfür irgendetwas Ernstes befürworten würde - es bricht allzu schnell bei realen Anforderungen ab (keine Lambdas, daher müssen wir Zeichenfolgen verwenden. Verschachtelungsfunktionsaufrufe mit Zeichenfolgen sind jedoch nicht möglich, Pipes sind nicht möglich usw.)

Jo So.
quelle
12
+1 Dieses Gerät ist nicht nur portabel, sondern auch lesbar, idiomatisch und elegant (für Shell-Skripte). Es erstreckt sich natürlich auch auf mehrere Muster; case $HOST in user01 | node* ) ...
Tripleee
Gibt es einen Namen für diese Art der Code-Formatierung? if case $HOST in node*) true;; *) false;; esac; then Ich habe es hier und da gesehen, für mein Auge sieht es irgendwie zerknittert aus.
Niels Bom
@NielsBom Ich weiß nicht genau, was Sie unter Formatierung verstehen, aber mein Punkt war, dass Shell-Code sehr komponierbar ist . Da caseBefehle Befehle sind, können sie hineingehen if ... then.
Jo So
Ich verstehe nicht einmal, warum es zusammensetzbar ist, ich verstehe nicht genug Shell-Skript dafür :-) Meine Frage ist, wie dieser Code nicht übereinstimmende Klammern und doppelte Semikolons verwendet. Es sieht nicht so aus wie das Shell-Skript, das ich zuvor gesehen habe, aber ich bin es vielleicht gewohnt, Bash-Skript mehr als Sh-Skript zu sehen, also könnte es das sein.
Niels Bom
Hinweis: Es sollte beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }anders sein, wenn $1es ein Literal gibt *oder ?es eine falsche Antwort geben könnte.
LLFourn
80

Sie können nur den Teil der Zeichenfolge auswählen, den Sie überprüfen möchten:

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

Für Ihre Folgefrage können Sie einen OP verwenden :

if [[ "$HOST" == user1 || "$HOST" == node* ]]
Martin Clayton
quelle
8
Sie sollten doppelt zitieren${HOST:0:4}
Jo So
@ Jo Also: Was ist der Grund?
Peter Mortensen
@ PeterMortensen, versuchen SieHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So
60

Ich bevorzuge die anderen bereits veröffentlichten Methoden, aber einige Leute verwenden gerne:

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

Bearbeiten:

Ich habe Ihre Alternativen zu der obigen case-Anweisung hinzugefügt

In Ihrer bearbeiteten Version haben Sie zu viele Klammern. Es sollte so aussehen:

if [[ $HOST == user1 || $HOST == node* ]];
Bis auf weiteres angehalten.
quelle
Danke, Dennis! Ich habe dem ursprünglichen Beitrag eine neue Frage hinzugefügt, wie Ausdrücke in if cluase kombiniert werden können.
Tim
11
"Manche Leute mögen ...": Dieser ist über Versionen und Shells hinweg portabler.
Carlosayam
Mit case-Anweisungen können Sie die Anführungszeichen um die Variable weglassen, da keine Wortaufteilung erfolgt. Ich weiß, dass es sinnlos und inkonsistent ist, aber ich lasse die Zitate dort gerne weg, um es lokal optisch ansprechender zu machen.
Jo So
Und in meinem Fall musste ich die Anführungszeichen vorher weglassen): "/ *") hat nicht funktioniert, / *) hat funktioniert. (Ich suche nach Strings, die mit /, dh absoluten Pfaden beginnen)
Josiah Yoder
36

Während ich die meisten Antworten hier ganz richtig finde, enthalten viele von ihnen unnötige Bashismen. Die POSIX-Parametererweiterung bietet Ihnen alles, was Sie brauchen:

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

und

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

${var#expr}Streifen die kleinste prefix Anpassung expraus ${var}und kehrt das. Wenn ${host}also nicht mit user( node) beginnt , ist ${host#user}( ${host#node}) dasselbe wie${host} .

exprerlaubt fnmatch()Wildcards, also ${host#node??}und Freunde arbeiten auch.

dhke
quelle
2
Ich würde argumentieren, dass der Bashismus [[ $host == user* ]]notwendig sein könnte, da er weitaus lesbarer ist als [ "${host#user}" != "${host}" ]. Wenn Sie also die Umgebung steuern, in der das Skript ausgeführt wird (auf die neuesten Versionen von bash), ist die erstere vorzuziehen.
X-Yuri
2
@ x-yuri Ehrlich gesagt würde ich das einfach in eine has_prefix()Funktion packen und es nie wieder anschauen.
Dhke
30

Da #Bash eine Bedeutung hat, bin ich zu folgender Lösung gekommen.

Außerdem packe ich lieber Strings mit "", um Leerzeichen usw. zu überwinden.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi
Ozma
quelle
Das war genau das, was ich brauchte. Vielen Dank!
Ionică Bizău
danke, ich habe mich auch gefragt, wie man eine Saite abfängt blah:, sieht so aus, als wäre dies die Antwort!
Anentropic
12

Mark Rushakoffs Antwort mit dem höchsten Rang wird ein kleines bisschen mehr Syntaxdetails hinzugefügt.

Der Ausdruck

$HOST == node*

Kann auch geschrieben werden als

$HOST == "node"*

Der Effekt ist der gleiche. Stellen Sie einfach sicher, dass sich der Platzhalter außerhalb des angegebenen Textes befindet. Befindet sich der Platzhalter in Anführungszeichen, wird er wörtlich interpretiert (dh nicht als Platzhalter).

Mr Kartoffelkopf
quelle
8

@OP, für beide Fragen können Sie case / esac verwenden:

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

Zweite Frage

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

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

Oder Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac
Ghostdog74
quelle
Beachten Sie, dass dies ;&nur in Bash> = 4 verfügbar ist.
Bis auf weiteres angehalten.
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

nicht funktioniert, weil alle [, [[und testerkennen die gleiche nicht - rekursive Grammatik. Siehe Abschnitt BEDINGTE AUSDRÜCKE auf Ihrer Bash-Manpage.

Nebenbei sagt der SUSv3

Der von KornShell abgeleitete bedingte Befehl (doppelte Klammer [[]] ) wurde in einem frühen Vorschlag aus der Beschreibung der Shell-Befehlssprache entfernt. Einwände erhoben wurden , dass das eigentliche Problem Missbrauch des ist Testbefehl ( [ ), und es in die Schale setzen , ist der falsche Weg , um das Problem zu beheben. Stattdessen sind eine ordnungsgemäße Dokumentation und ein neues Shell-reserviertes Wort ( ! ) Ausreichend.

Tests , die mehrere erfordern Test an der Shell - Ebene durchgeführt werden unter Verwendung einzelner Invokationen der Operationen können Testbefehl und Shell Logicals, eher das fehlerträchtige Verwendung als -o Flagge Test .

Sie müssten es so schreiben, aber der Test unterstützt es nicht:

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

Test verwendet = für die Zeichenfolgengleichheit und unterstützt vor allem keinen Mustervergleich.

caseIch esachabe eine gute Unterstützung für den Mustervergleich:

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

Es hat den zusätzlichen Vorteil, dass es nicht von Bash abhängt und die Syntax portabel ist. Von der Single Unix Specification , The Shell Command Language :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac
nur jemand
quelle
1
[und testsind Bash Builtins sowie externe Programme. Versuchen Sie es type -a [.
Bis auf weiteres angehalten.
Vielen Dank für die Erklärung der Probleme mit der "Verbindung oder", @just jemand - suchte genau nach so etwas! Prost! PS-Hinweis (unabhängig von OP) : if [ -z $aa -or -z $bb ]; ... gibt " bash: [: -or: binärer Operator erwartet "; geht jedoch if [ -z "$aa" -o -z "$bb" ] ; ...vorbei.
Sdaau
2

grep

Dies ist POSIX und sieht besser aus als caseLösungen:

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

Erläuterung:

Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功
quelle
2

Ich habe die Antwort von @ markrushakoff optimiert, um sie zu einer aufrufbaren Funktion zu machen:

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" == "" ]]
}

Verwenden Sie es so:

$ 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

Hier ist eine komplexere Version, die einen angegebenen Standardwert vorsieht:

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* ]]
}

Verwenden Sie es so:

$ 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
Mike Slinn
quelle
-5

Eine andere Sache, die Sie tun können, ist catherauszufinden, was Sie wiedergeben und mit was Sie pfeifeninline cut -c 1-1

Richard Tang
quelle