Bagaimana cara memeriksa apakah ada variabel dalam daftar di BASH

145

Saya mencoba menulis skrip di bash yang memeriksa validitas input pengguna.
Saya ingin mencocokkan input (katakanlah variabel x) dengan daftar nilai yang valid.

apa yang saya temukan saat ini adalah:

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

Pertanyaan saya adalah apakah ada cara yang lebih sederhana untuk melakukan ini,
seperti list.contains(x)untuk sebagian besar bahasa pemrograman.

Tambahan:
Katakanlah daftar adalah:

list="11 22 33"

kode saya akan menggemakan pesan hanya untuk nilai-nilai itu karena listdiperlakukan sebagai array dan bukan string, semua manipulasi string akan divalidasi 1sementara saya ingin gagal.

Ofir Farchy
sumber

Jawaban:

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

atau buat fungsi:

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

untuk menggunakannya:

contains aList anItem
echo $? # 0: match, 1: failed
chemila
sumber
38
harus[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov
12
Dapat memberikan positif palsu jika masukan pengguna berisi karakter khusus ekspresi reguler, misalnyax=.
glenn jackman
4
Ini akan memberi ada positif palsu / negatif: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
6
solusi yang lebih ringkas: [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. ini benar dengan asumsi spasi adalah pemisah dan $xtidak berisi spasi
Tianren Liu
2
Saya pikir lebih baik menggunakan fungsi "dalam" isIn()sehingga Anda dapat menulis item terlebih dahulu dalam parameter. Anda juga dapat melakukan echosesuatu selain menggunakan exitseperti ini: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Jadi Anda dapat menggunakan fungsi ini dengan cara ini:result=$(isIn "-t" "-o -t 45") && echo $result
hayj
35

Matvey benar, tetapi Anda harus mengutip $ x dan mempertimbangkan segala jenis "spasi" (misalnya baris baru) dengan

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

jadi, yaitu

# 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
}

berakhir kemudian

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

atau

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

Perhatikan bahwa Anda harus menggunakan ""dalam kasus variabel:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
Oriettaxx
sumber
3
Solusi ini berfungsi meskipun $itemberisi karakter khusus, seperti .(tetapi $listvariabel mungkin perlu dikutip di dalam pengujian). Dan fungsi dapat didefinisikan lebih sederhana: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
tidak berfungsi dalam kasus seperti: list="aa bb xx cc ff"danx="aa bb"
creativeChips
Halo. Saya mencoba mempelajari dasar-dasar bashscript dan saya ingin tahu bagaimana Anda bisa mengurai string daftar, ke dalam daftar, dan Anda melakukan pemeriksaan yang ada di daftar itu. Saya dapat memahami Anda berpisah menggunakan spasi tetapi saya tidak dapat sepenuhnya memahami ekspresinya. Dapatkah Anda memberikan saya kata kunci untuk Google dasar-dasar ungkapan ini: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Atau, jika Anda punya waktu, saya akan senang mendengar penjelasan Anda untuk ungkapan ini. Catatan: Saya kira itu ekspresi regex (dimulai dengan ^ dll) tapi saya tidak tahu apa artinya = ~. Jadi saya ingin penjelasan keseluruhan: P
Ali Yılmaz
30

bagaimana tentang

echo $list | grep -w -q $x

Anda dapat memeriksa output atau $?baris di atas untuk membuat keputusan.

grep -wmemeriksa pola kata keseluruhan. Menambahkan -qmencegah menggemakan daftar.

Kent
sumber
1
bukankah ini memvalidasi "val" jika "nilai" valid (yaitu dalam daftar) karena ini adalah substringnya
Ofir Farchy
Ya, seperti halnya jawaban yang diterima. @glennjackman memberikan solusi yang berhasil.
f.ardelian
3
Anda dapat menggunakan grep -q untuk membuat grep menjadi tenang
Amir
ini juga akan menerima kecocokan parsial, yang mungkin bukan yang diinginkan pengguna
carnicer
3
@carnicer lalu gunakan saja grep -w $ x untuk mendapatkan kecocokan yang tepat
pengguna1297406
29

Solusi termudah IMHO adalah menambahkan spasi di awal dan menambahkan string asli dengan spasi dan memeriksa regex dengan [[ ]]

haystack='foo bar'
needle='bar'

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

ini tidak akan menjadi positif palsu pada nilai dengan nilai yang mengandung jarum sebagai substring, misalnya dengan tumpukan jerami foo barbaz.

(Konsep ini dicuri tanpa malu-malu dari hasClass()-Method JQuery )

blaimi
sumber
4
jika pemisahnya bukan spasi, solusinya lebih jelas. itu juga dapat dilakukan tanpa regex: haystack="foo:bar"dan[[ ":$haystack:" = *:$needle:* ]]
phiphi
19

Anda juga dapat menggunakan (* karakter pengganti) di luar pernyataan kasus, jika Anda menggunakan tanda kurung ganda:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi
Mithun Sasidharan
sumber
3
Sederhana dan elegan, +1
João Pereira
14
Ini akan memberikan positif palsu jika "My" adalah substring dari string lain, misalnya string = 'MyCommand string'.
Luke Lee
Perlu diperhatikan bahwa karakter pengganti ( *My*) harus berada di sisi kanan pengujian.
Jacob Vanus
10

Jika daftar nilai Anda akan di-hardcode dalam skrip, cukup mudah untuk menguji menggunakan case. Berikut adalah contoh singkat, yang dapat Anda sesuaikan dengan kebutuhan Anda:

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

Jika daftarnya adalah variabel array pada waktu proses, salah satu jawaban lain mungkin lebih cocok.

Toby Speight
sumber
8

Jika daftar diperbaiki dalam skrip, saya paling suka yang berikut ini:

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

Kemudian gunakan validate "$x"untuk menguji apakah $xdiizinkan.

Jika Anda menginginkan satu baris, dan tidak peduli dengan spasi kosong pada nama item, Anda dapat menggunakan ini (pemberitahuan -wdaripada -x):

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

Catatan:

  • Ini shsesuai dengan POSIX .
  • validatetidak tidak menerima substring (menghapus -xpilihan untuk grep jika Anda ingin itu).
  • validatemenafsirkan argumennya sebagai string tetap, bukan ekspresi reguler (hapus -Fopsi ke grep jika Anda menginginkannya).

Kode contoh untuk menjalankan fungsinya:

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
sumber
6

Pertimbangkan untuk mengeksploitasi kunci array asosiatif . Saya akan menganggap ini mengungguli pencocokan regex / pola dan perulangan, meskipun saya belum membuat profilnya.

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

Keluaran:

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 )
RubyTuesdayDONO
sumber
2
... dan Anda dapat menyederhanakan bahwa pengganti (dan tidak bergantung pada echo -n) dengan penggunaan kreatif parameter: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight
Apakah ada cara mudah untuk membuat array seperti itu jika saya hanya memiliki daftar (panjang) seperti `list =" one two three xyz ... "?
Ott Toomet
5

Saya merasa lebih mudah menggunakan formulir echo $LIST | xargs -n1 echo | grep $VALUEseperti yang diilustrasikan di bawah ini:

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

Ini berfungsi untuk daftar yang dipisahkan spasi, tetapi Anda dapat menyesuaikannya dengan pembatas lain (seperti :) dengan melakukan hal berikut:

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

Perhatikan bahwa "diperlukan agar tes dapat bekerja.

Sébastien Pierre
sumber
Ini akan menyebabkan LIST="SOMEITEM1 ITEM2"pengembalian menjadi benar meskipun ITEM1tidak ada di dalamnya
Ofir Farchy
Tangkapan bagus, saya memperbarui contoh dengan grep -e untuk mengecualikan kecocokan parsial.
Sébastien Pierre
Saya yakin ada tambahan `di akhir pernyataan" jika ". Bentuk yang benar adalah: [-n "ʻecho $ LIST | xargs -n1 echo | grep -e \" ^ $ VALUE $ \ "]
Elad Tabak
3

Pikir saya akan menambahkan solusi saya ke daftar.

# 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
sumber
1

Contoh

$ 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

in_list

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
sumber
1

Dengan asumsi variabel TARGET hanya dapat 'binomial' atau 'regresi', maka berikut ini akan dilakukan:

# 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

Anda dapat menambahkan lebih banyak string ke dalam daftar dengan memisahkannya dengan | (pipa) karakter.

Keuntungan menggunakan egrep, adalah Anda dapat dengan mudah menambahkan case insensitivity (-i), atau memeriksa skenario yang lebih kompleks dengan ekspresi reguler.

Tagar
sumber
1

Ini hampir merupakan proposal asli Anda, tetapi hampir 1 baris. Tidak serumit jawaban valid lainnya, dan tidak begitu tergantung pada versi bash (dapat berfungsi dengan bash lama).

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 ; }

Saya kira proposal saya masih bisa ditingkatkan, baik panjang maupun gayanya.

carnicer
sumber
1

Jika tidak terlalu lama; Anda bisa merangkainya di antara kesetaraan sepanjang perbandingan OR logis seperti itu.

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

Saya memiliki masalah yang tepat ini dan sementara yang di atas jelek, lebih jelas apa yang sedang terjadi daripada solusi umum lainnya.

Kelly Trinh
sumber
1

Kompgen bawaan shell dapat membantu di sini. Ia dapat mengambil daftar dengan flag -W dan mengembalikan setiap kecocokan potensial yang ditemukannya.

# My list can contain spaces so I want to set the internal
# file separator to newline to preserve the original strings.
IFS=$'\n'

# Create a list of acceptable strings.
accept=( 'foo' 'bar' 'foo bar' )

# The string we will check
word='foo'

# compgen will return a list of possible matches of the 
# variable 'word' with the best match being first.
compgen -W "${accept[*]}" "$word"

# Returns:
# foo
# foo bar

Kita bisa menulis fungsi untuk menguji apakah sebuah string sama dengan string yang paling cocok. Ini memungkinkan Anda mengembalikan 0 atau 1 untuk lulus atau gagal masing-masing.

function validate {
  local IFS=$'\n'
  local accept=( 'foo' 'bar' 'foo bar' )
  if [ "$1" == "$(compgen -W "${accept[*]}" "$1" | head -1)" ] ; then
    return 0
  else
    return 1
  fi
}

Sekarang Anda dapat menulis pengujian yang sangat bersih untuk memvalidasi jika sebuah string dapat diterima.

validate "blah" || echo unacceptable

if validate "foo" ; then
  echo acceptable
else 
  echo unacceptable
fi
Qub3r
sumber
0

Solusi alternatif yang terinspirasi oleh respons yang diterima, tetapi itu menggunakan logika terbalik:

MODE="${1}"

echo "<${MODE}>"
[[ "${MODE}" =~ ^(preview|live|both)$ ]] && echo "OK" || echo "Uh?"

Di sini, input ($ MODE) harus menjadi salah satu opsi dalam ekspresi reguler ('preview', 'live', atau 'keduanya'), berlawanan dengan mencocokkan seluruh daftar opsi dengan input pengguna. Tentu saja, Anda tidak mengharapkan ekspresi reguler berubah.

Rob Aceves
sumber