Jak sprawdzić, czy zmienna istnieje na liście w BASH

137

Próbuję napisać skrypt w bash, który sprawdza poprawność danych wejściowych użytkownika.
Chcę dopasować dane wejściowe (powiedzmy zmienną x) do listy prawidłowych wartości.

w tej chwili wymyśliłem:

for item in $list
do
    if [ "$x" == "$item" ]; then
        echo "In the list"
        exit
    fi
done

Moje pytanie brzmi, czy istnieje prostszy sposób na zrobienie tego,
coś w rodzaju a list.contains(x)dla większości języków programowania.

Dodatek:
Lista wypowiedzi to:

list="11 22 33"

mój kod powtórzy wiadomość tylko dla tych wartości, ponieważ listjest traktowany jako tablica, a nie jako ciąg, wszystkie operacje na ciągach zostaną sprawdzone, 1podczas gdy chciałbym, aby zakończyło się niepowodzeniem.

Ofir Farchy
źródło

Odpowiedzi:

142
[[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'

lub utwórz funkcję:

contains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
}

aby go użyć:

contains aList anItem
echo $? # 0: match, 1: failed
chemila
źródło
37
powinno być[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov
12
Może dawać fałszywie dodatnie, jeśli dane wejściowe użytkownika zawierają znaki specjalne wyrażeń regularnych, na przykładx=.
glenn jackman
4
To daje żadnych fałszywych pozytywy / negatywy: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
6
bardziej zwięzły rozwiązanie: [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. jest poprawne zakładając, że spacja jest separatorem i $xnie zawiera spacji
Tianren Liu,
2
Myślę, że lepiej jest użyć funkcji „jest w”, isIn()aby można było najpierw zapisać element w parametrach. Możesz też echocoś zamiast używać w exitten sposób: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Więc możesz użyć funkcji w ten sposób:result=$(isIn "-t" "-o -t 45") && echo $result
hayj
34

Matvey ma rację, ale powinieneś zacytować $ x i rozważyć wszelkiego rodzaju "spacje" (np. Nowy wiersz) z

[[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 

więc tj

# list_include_item "10 11 12" "2"
function list_include_item {
  local list="$1"
  local item="$2"
  if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
    # yes, list include item
    result=0
  else
    result=1
  fi
  return $result
}

wtedy koniec

`list_include_item "10 11 12" "12"`  && echo "yes" || echo "no"

lub

if `list_include_item "10 11 12" "1"` ; then
  echo "yes"
else 
  echo "no"
fi

Zauważ, że musisz użyć ""w przypadku zmiennych:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
Oriettaxx
źródło
3
To rozwiązanie działa nawet, jeśli $itemzawiera specjalne znaki, takie jak .(ale $listzmienna prawdopodobnie wymaga cytowania w teście). A funkcja może być zdefiniowana jeszcze prostsze: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
nie działa w przypadkach takich jak: list="aa bb xx cc ff"ix="aa bb"
creativeChips
dzień dobry. Próbuję nauczyć się podstaw bashscriptu i chciałem wiedzieć, jak można przetworzyć ciąg z listy na listę i sprawdzić istnienie na tej liście. Rozumiem, że podzieliłeś się spacją, ale nie mogłem w pełni zrozumieć wyrażenia. Można zapewnić mi słów kluczowych Google podstaw tego wyrażenia: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Lub, jeśli masz czas, z przyjemnością usłyszę wyjaśnienie tego wyrażenia. Uwaga: wydaje mi się, że jest to wyrażenie regularne (zaczyna się od ^ itd.), Ale nie wiem, co oznacza = ~. Chciałbym więc uzyskać ogólne wyjaśnienie: P
Ali Yılmaz,
28

Najłatwiejszym rozwiązaniem IMHO jest wstawienie i dołączenie oryginalnego ciągu spacją i sprawdzenie wyrażenia regularnego za pomocą [[ ]]

haystack='foo bar'
needle='bar'

if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
    ...
fi

nie będzie to fałszywie dodatnie w przypadku wartości zawierających igłę jako podciąg, np. ze stogiem siana foo barbaz.

(Koncepcja została bezwstydnie skradziona z JQuery's hasClass() metody JQuery'ego)

blaimi
źródło
4
jeśli separatorem nie jest spacja, rozwiązanie jest bardziej oczywiste. można to również zrobić bez wyrażenia regularnego: haystack="foo:bar"i[[ ":$haystack:" = *:$needle:* ]]
phiphi
25

Co powiesz na

echo $list | grep -w $x

$?aby podjąć decyzję, możesz sprawdzić dane wyjściowe lub powyższą linię.

grep -w sprawdza całe wzorce słów.

Kent
źródło
1
czy to nie sprawdzałoby wartości „val”, gdyby „wartość” była prawidłowa (tj. na liście), ponieważ jest to jej podciąg
Ofir Farchy
Tak, tak, podobnie jak przyjęta odpowiedź. @glennjackman daje działające rozwiązanie.
f.ardelian,
3
możesz użyć grep -q, aby grep był cichy
Amir
zaakceptowałoby to również częściowe dopasowania, które mogą nie być tym, czego chce użytkownik
karnicer
3
@carnicer następnie po prostu użyj grep -w $ x, aby uzyskać dokładne dopasowanie
user1297406
18

Możesz również użyć (* symboli wieloznacznych) poza instrukcją case, jeśli używasz podwójnych nawiasów:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi
Mithun Sasidharan
źródło
3
Prosty i elegancki, +1
João Pereira
14
To da fałszywie dodatni, jeśli „My” jest podłańcuchem innego łańcucha, np. String = 'MyCommand string'.
Luke Lee
Warto zauważyć, że symbole wieloznaczne ( *My*) muszą znajdować się po prawej stronie testu.
Jacob Vanus
8

Jeśli lista wartości ma być zakodowana na stałe w skrypcie, jej przetestowanie przy użyciu jest dość proste case. Oto krótki przykład, który możesz dostosować do swoich wymagań:

for item in $list
do
    case "$x" in
      item1|item2)
        echo "In the list"
        ;;
      not_an_item)
        echo "Error" >&2
        exit 1
        ;;
    esac
done

Jeśli lista jest zmienną tablicową w czasie wykonywania, prawdopodobnie lepiej pasuje jedna z pozostałych odpowiedzi.

Toby Speight
źródło
7

Jeśli lista jest naprawiona w skrypcie, najbardziej lubię następujące:

validate() {
    grep -F -q -x "$1" <<EOF
item 1
item 2
item 3
EOF
}

Następnie użyj, validate "$x"aby sprawdzić, czy$x jest dozwolone.

Jeśli chcesz mieć jedną linijkę i nie obchodzą Cię białe znaki w nazwach elementów, możesz użyć tego (uwaga -wzamiast -x):

validate() { echo "11 22 33" | grep -F -q -w "$1"; }

Uwagi:

  • Jest to shzgodne z POSIX .
  • validatema nie przyjąć podciągi (usunąć -xopcję grep jeśli chcesz to).
  • validateinterpretuje jej argument jako ustalony ciąg znaków, a nie wyrażenie regularne (usuń -Fopcję grep, jeśli chcesz).

Przykładowy kod do wykonania funkcji:

for x in "item 1" "item2" "item 3" "3" "*"; do
    echo -n "'$x' is "
    validate "$x" && echo "valid" || echo "invalid"
done
Søren Løvborg
źródło
6

Rozważ wykorzystanie kluczy tablic asocjacyjnych . Zakładam, że to przewyższa zarówno dopasowywanie wyrażeń regularnych / wzorców, jak i zapętlanie, chociaż nie sprofilowałem tego.

declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
for value in one two three four
do
    echo -n "$value is "
    # a missing key expands to the null string, 
    # and we've set each interesting key to a non-empty value
    [[ -z "${list[$value]}" ]] && echo -n '*not* '
    echo "a member of ( ${!list[*]} )"
done

Wynik:

one is a member of ( one two three )
two is a member of ( one two three )
three is a member of ( one two three )
four is *not* a member of ( one two three )
RubyTuesday DONO
źródło
2
... i można uprościć, że zastąpienie (a nie polegać na echo -n) z twórczego wykorzystania parametrów: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight
Czy istnieje łatwy sposób na utworzenie takiej tablicy, jeśli mam tylko (długą) listę, taką jak `list =" jeden, dwa, trzy xyz ... "?
Ott Toomet
5

Uważam, że łatwiej jest korzystać z formularza, echo $LIST | xargs -n1 echo | grep $VALUEjak pokazano poniżej:

LIST="ITEM1 ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
    ...
fi

Działa to w przypadku listy oddzielonej spacjami, ale możesz dostosować ją do dowolnego innego separatora (np. :), Wykonując następujące czynności:

LIST="ITEM1:ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
   ...
fi

Zwróć uwagę, że "są wymagane, aby test zadziałał.

Sébastien Pierre
źródło
Spowoduje to LIST="SOMEITEM1 ITEM2"powrót prawdy, mimo że ITEM1jej w niej nie ma
Ofir Farchy
Dobry chwyt, zaktualizowałem przykład za pomocą grep -e, aby wykluczyć częściowe dopasowanie.
Sébastien Pierre
Uważam, że na końcu stwierdzenia „jeśli” znajduje się dodatkowe słowo „jeśli”. Prawidłowa forma to: [-n "` echo $ LISTA | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak
3

Pomyślałem, że dodam moje rozwiązanie do listy.

# Checks if element "$1" is in array "$2"
# @NOTE:
#   Be sure that array is passed in the form:
#       "${ARR[@]}"
elementIn () {
    # shopt -s nocasematch # Can be useful to disable case-matching
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}

# Usage:
list=(11 22 33)
item=22

if elementIn "$item" "${list[@]}"; then
    echo TRUE;
else
    echo FALSE
fi
# TRUE

item=44
elementIn $item "${list[@]}" && echo TRUE || echo FALSE
# FALSE
SamWN
źródło
1

Przykłady

$ in_list super test me out
NO

$ in_list "super dude" test me out
NO

$ in_list "super dude" test me "super dude"
YES

# How to use in another script
if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
then
  echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
  exit;
fi

na liście

function show_help()
{
  IT=$(CAT <<EOF

  usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...

  e.g. 

  a b c d                    -> NO
  a b a d                    -> YES
  "test me" how "test me"    -> YES

  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

if [ "$#" -eq 0 ]; then
  show_help
fi

SEARCH_FOR=$1
shift;

for ITEM in "$@"
do
  if [ "$SEARCH_FOR" == "$ITEM" ]
  then
    echo "YES"
    exit;
  fi
done

echo "NO"
Brad Parks
źródło
1

Zakładając, że zmienna TARGET może być tylko „dwumianowa” lub „regresja”, to wystarczyłoby:

# Check for modeling types known to this script
if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
    echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
    usage
fi

Możesz dodać więcej ciągów do listy, oddzielając je znakiem | (kreska).

Zaletą używania egrep jest to, że można łatwo dodać niewrażliwość na wielkość liter (-i) lub sprawdzić bardziej złożone scenariusze za pomocą wyrażenia regularnego.

Tagar
źródło
1

To jest prawie twoja oryginalna propozycja, ale prawie 1-liniowa. Nie jest to tak skomplikowane, jak inne prawidłowe odpowiedzi, i nie tak w zależności od wersji basha (może działać ze starymi bashesami).

OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
[ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }

Myślę, że moją propozycję można jeszcze poprawić, zarówno pod względem długości, jak i stylu.

carnicer
źródło
0

Jeśli to nie jest zbyt długie; możesz po prostu umieścić je między równościami wzdłuż logicznego porównania LUB, w ten sposób.

if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
    echo In the list
fi 

Miałem dokładnie ten problem i chociaż powyższe jest brzydkie, bardziej oczywiste jest, co się dzieje, niż inne uogólnione rozwiązania.

Kelly Trinh
źródło