Jak w Bash mogę sprawdzić, czy ciąg zaczyna się od pewnej wartości?

744

Chciałbym sprawdzić, czy ciąg zaczyna się od „węzła”, np. „Węzła001”. Coś jak

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

Jak mogę to zrobić poprawnie?


Ponadto muszę połączyć wyrażenia, aby sprawdzić, czy HOST to „użytkownik1” lub zaczyna się od „węzeł”

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

> > > -bash: [: too many arguments

Jak mogę to zrobić poprawnie?

Tim
źródło
7
Nie ulegaj pokusie łączenia wyrażeń. Może wyglądać gorzej, gdy dwa oddzielne warunki warunkowe, ale można podawać lepsze komunikaty o błędach i ułatwić debugowanie skryptu. Również unikałbym funkcji bash. Przełącznik jest właściwą drogą.
hendry

Odpowiedzi:

1072

Ten fragment w Advanced Bash Scripting Guide mówi:

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

Więc miałeś prawie poprawną; potrzebowaliście podwójnych nawiasów, a nie pojedynczych nawiasów.


W odniesieniu do drugiego pytania możesz napisać to w następujący sposób:

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

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

Który odbije się echem

yes1
yes2

ifTrudno się przyzwyczaić do składni Basha (IMO).

Mark Rushakoff
źródło
12
Dla wyrażenia regularnego masz na myśli [[$ a = ~ ^ z. *]]?
JStrahl
3
Czy istnieje funkcjonalna różnica między [[ $a == z* ]]i [[ $a == "z*" ]]? Innymi słowy: czy działają inaczej? A co konkretnie masz na myśli, mówiąc „$ a jest równe z *”?
Niels Bom,
5
Nie potrzebujesz separatora instrukcji ";" jeśli umieścisz „następnie” we własnej linii
Yaza
6
Tylko dla kompletności: Aby sprawdzić, czy łańcuch KONIEC z ...:[[ $a == *com ]]
lepe
4
ABS to niefortunny wybór referencji - to w dużej mierze W3Schools bash, pełne nieaktualnych treści i przykładów złych praktyk; kanał freenode #bash stara się zniechęcić do korzystania z niego przynajmniej od 2008 roku . Czy jest jakaś szansa na ponowne powołanie na BashFAQ # 31 ? (Zasugerowałbym również wiki Bash-Hackerów, ale już nie działa).
Charles Duffy,
207

Jeśli używasz najnowszej wersji Bash (v3 +), proponuję operator porównania wyrażeń regularnych Bash =~, na przykład:

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

Aby dopasować this or thatwyrażenie regularne, użyj |na przykład:

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

Uwaga - jest to „właściwa” składnia wyrażeń regularnych.

  • user*oznacza usei zero lub więcej wystąpień r, więc usei userrrrbędzie pasować.
  • user.*środki useri zero-or-więcej wystąpień dowolnego znaku, tak user1, userXbędzie pasował.
  • ^user.*oznacza dopasowanie wzorca user.*na początku $ HOST.

Jeśli nie znasz składni wyrażeń regularnych, spróbuj odwołać się do tego zasobu .

Brabster
źródło
Dzięki, Brabster! Do oryginalnego postu dodałem nowe pytanie dotyczące łączenia wyrażeń w if cluase.
Tim
2
Szkoda, że ​​przyjęta odpowiedź nie mówi nic o składni wyrażeń regularnych.
CarlosRos
20
Dla twojej informacji =~operator Bash dopasowuje wyrażenie regularne tylko wtedy, gdy prawa strona jest UNQUOTED. Jeśli zacytujesz prawą stronę: „Można zacytować dowolną część wzoru, aby wymusić dopasowanie go jako łańcucha”. (1.) upewnij się, że zawsze umieszczasz wyrażenia regularne po prawej stronie bez cudzysłowu i (2.) jeśli przechowujesz swoje wyrażenie regularne w zmiennej, pamiętaj, aby NIE podawać prawej strony podczas wykonywania interpretacji parametrów.
Trevor Boyd Smith
145

Zawsze staram się trzymać POSIX shzamiast używać rozszerzeń Bash, ponieważ jednym z głównych punktów skryptów jest przenośność (oprócz łączenia programów, a nie ich zastępowania).

W shistnieje łatwy sposób sprawdzić dla „is-prefix” stanie.

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

Biorąc pod uwagę wiek, tajemny i kruchy sh (a Bash nie jest lekarstwem: jest bardziej skomplikowany, mniej spójny i mniej przenośny), chciałbym zwrócić uwagę na bardzo fajny aspekt funkcjonalny: chociaż niektóre elementy składniowe casesą wbudowane , powstałe konstrukcje nie różnią się niczym od innych zadań. Można je składać w ten sam sposób:

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

Lub nawet krócej

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

Lub nawet krócej (tylko !jako element językowy - ale teraz jest to zły styl)

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

Jeśli lubisz wyrażać się jasno, zbuduj swój własny element językowy:

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

Czy to nie jest całkiem miłe?

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

A ponieważ shzasadniczo są to tylko zadania i listy ciągów (i procesy wewnętrzne, z których składają się zadania), możemy teraz nawet wykonać lekkie programowanie funkcjonalne:

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

To jest eleganckie. Nie, że zalecałbym używanie shdo czegoś poważnego - zbyt szybko psuje się w rzeczywistych wymaganiach (brak lambd, więc musimy używać łańcuchów. Ale zagnieżdżanie wywołań funkcji za pomocą łańcuchów nie jest możliwe, potoki nie są możliwe itp.)

Jo So
źródło
12
+1 Jest nie tylko przenośny, ale także czytelny, idiomatyczny i elegancki (dla skryptu powłoki). Rozciąga się także naturalnie na wiele wzorów; case $HOST in user01 | node* ) ...
tripleee
Czy istnieje nazwa tego typu formatowania kodu? if case $HOST in node*) true;; *) false;; esac; then Widziałem to tu i tam, dla mnie wygląda to na trochę zgniecione.
Niels Bom
@NielsBom Nie wiem, co dokładnie masz na myśli formatowanie, ale chodzi mi o to, że Shellcode jest bardzo sk . Ponieważ casepolecenia są poleceniami, mogą wejść do środka if ... then.
Jo So
Nawet nie rozumiem, dlaczego można go skomponować, nie rozumiem wystarczająco dużo skryptu powłoki :-) Moje pytanie dotyczy tego, w jaki sposób ten kod używa niedopasowanych nawiasów i podwójnych średników. To nie przypomina skryptu powłoki, który widziałem wcześniej, ale mogę być przyzwyczajony do oglądania skryptu bash bardziej niż sh, więc to może być to.
Niels Bom,
Uwaga: beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }inaczej powinno być, jeśli $1ma literał *lub ?może dać złą odpowiedź.
LLFourn
80

Możesz wybrać tylko część ciągu, którą chcesz sprawdzić:

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

W kolejnym pytaniu możesz użyć OR :

if [[ "$HOST" == user1 || "$HOST" == node* ]]
Martin Clayton
źródło
8
Powinieneś zacytować${HOST:0:4}
Jo So
@ Jo Więc: Jaki jest powód?
Peter Mortensen
@PeterMortensen, spróbujHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So
60

Wolę inne metody już opublikowane, ale niektórzy ludzie lubią używać:

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

Edytować:

Dodałem twoje alternatywy do powyższego oświadczenia

W edytowanej wersji masz za dużo nawiasów. To powinno wyglądać tak:

if [[ $HOST == user1 || $HOST == node* ]];
Wstrzymano do odwołania.
źródło
Dzięki, Dennis! Do oryginalnego postu dodałem nowe pytanie dotyczące łączenia wyrażeń w if cluase.
Tim
11
„niektórzy lubią ...”: ten jest bardziej przenośny między wersjami i powłokami.
carlosayam
Za pomocą instrukcji case możesz pominąć cudzysłowy wokół zmiennej, ponieważ nie występuje dzielenie słów. Wiem, że jest to bezcelowe i niespójne, ale lubię pomijać cytaty, aby lokalnie było bardziej atrakcyjne wizualnie.
Jo So
I w moim przypadku musiałem wcześniej pominąć cytaty): „/ *”) nie działało, / *) działało. (Szukam ciągów zaczynających się od /, tj.
Ścieżek
36

Chociaż uważam, że większość odpowiedzi tutaj jest całkiem poprawna, wiele z nich zawiera niepotrzebne Bashizmy. Rozszerzenie parametrów POSIX zapewnia wszystko, czego potrzebujesz:

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

i

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

${var#expr}usuwa najmniejsze dopasowanie przedrostka exprz ${var}i zwraca to. Stąd jeśli ${host}nie nie zaczynać user( node), ${host#user}( ${host#node}) jest taka sama jak${host} .

exprpozwala na stosowanie fnmatch()symboli wieloznacznych, ${host#node??}a zatem i przyjaciele również działają.

dhke
źródło
2
Twierdziłbym, [[ $host == user* ]]że bashizm może być konieczny, ponieważ jest o wiele bardziej czytelny niż [ "${host#user}" != "${host}" ]. Jako że pod warunkiem, że kontrolujesz środowisko, w którym skrypt jest wykonywany (celuj w najnowsze wersje bash), to pierwsze jest preferowane.
x-yuri
2
@ x-yuri Szczerze mówiąc, po prostu spakuję to w has_prefix()funkcję i nigdy więcej na nią nie spojrzę.
dhke,
30

Od # ma to znaczenie w Bash, doszedłem do następującego rozwiązania.

Ponadto wolę spakować ciągi znaków „”, aby pokonać spacje itp.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi
ozma
źródło
Właśnie tego potrzebowałem. Dzięki!
Ionică Bizău,
dzięki, zastanawiałem się również, jak dopasować ciąg rozpoczynający się od blah:, wygląda na to, że to jest odpowiedź!
Anentropic
12

Dodając nieco więcej szczegółów składniowych do najwyższej odpowiedzi Marka Rushakoffa.

Ekspresja

$HOST == node*

Można również zapisać jako

$HOST == "node"*

Efekt jest taki sam. Upewnij się tylko, że symbol wieloznaczny znajduje się poza cytowanym tekstem. Jeśli symbol wieloznaczny znajduje się w cudzysłowie, będzie interpretowany dosłownie (tj. Nie jako symbol wieloznaczny).

MrPotatoHead
źródło
8

@OP, w obu pytaniach możesz użyć case / esac:

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

Drugie Pytanie

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

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

Lub Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac
ghostdog74
źródło
Pamiętaj, że ;&jest dostępne tylko w Bash> = 4.
Wstrzymano do odwołania.
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

nie działa, ponieważ wszystkie [, [[i testrozpoznać tę samą gramatykę nierekurencyjnym. Patrz sekcja WYRAŻENIA WARUNKOWE na stronie użytkownika Bash.

Nawiasem mówiąc, SUSv3 mówi

Polecenie warunkowe wyprowadzone z KornShell (podwójny nawias [[]] ) zostało usunięte z opisu języka poleceń powłoki we wczesnej propozycji. Zgłaszano zastrzeżenia, że ​​prawdziwym problemem jest niewłaściwe użycie polecenia test ( [ ), a umieszczenie go w powłoce jest złym sposobem na rozwiązanie problemu. Zamiast tego odpowiednia dokumentacja i nowe słowo zastrzeżone powłoki ( ! wystarczająca jest ).

Testy wymagające wielu testów operacje mogą być wykonywane na poziomie powłoki stosując indywidualne wywołaniami Test dowodzenia i Shell logicals, zamiast błędu podatne -o banderą testu .

Musisz napisać to w ten sposób, ale test go nie obsługuje:

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

test używa = do równości łańcuchów, a co ważniejsze, nie obsługuje dopasowania wzorca.

case/ esacma dobre wsparcie dla dopasowania wzorca:

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

Ma tę dodatkową zaletę, że nie zależy od Bash, a składnia jest przenośna. Ze specyfikacji Single Unix , język poleceń powłoki :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac
tylko ktoś
źródło
1
[i testsą wbudowane w Bash, a także programy zewnętrzne. Spróbować type -a [.
Wstrzymano do odwołania.
Wielkie dzięki za wyjaśnienie problemów z „związkiem lub”, właśnie ktoś - właśnie szukałem czegoś takiego! Twoje zdrowie! PS uwaga (niezwiązane z OP) if [ -z $aa -or -z $bb ]; ... daje „ bash: [: -or: operator binarny oczekiwany ”; jednak if [ -z "$aa" -o -z "$bb" ] ; ...mija.
sdaau
2

grep

Zapominając o wydajności, jest to POSIX i wygląda ładniej niż caserozwiązania:

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

Wyjaśnienie:

Ciro Santilli
źródło
2

Poprawiłem odpowiedź @ markrushakoff, aby stała się funkcją wywoływalną:

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

Użyj tego w ten sposób:

$ 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

Oto bardziej złożona wersja, która zapewnia określoną wartość domyślną:

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

Użyj tego w ten sposób:

$ 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
źródło
-5

Inną rzeczą, którą możesz zrobić, jest catwyjście z echa i potokówinline cut -c 1-1

Richard Tang
źródło