¿Cómo verifico si existe una variable en una lista en BASH?

138

Estoy tratando de escribir un script en bash que verifique la validez de una entrada del usuario.
Quiero hacer coincidir la entrada (digamos variable x) con una lista de valores válidos.

Lo que se me ocurrió en este momento es:

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

Mi pregunta es si hay una forma más sencilla de hacer esto,
algo así como list.contains(x)para la mayoría de los lenguajes de programación.

Adición:
Dicha lista es:

list="11 22 33"

mi código hará eco del mensaje solo para esos valores, ya que listse trata como una matriz y no como una cadena, todas las manipulaciones de la cadena se validarán 1mientras que yo quiero que falle.

Ofir Farchy
fuente

Respuestas:

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

o crear una función:

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

para usarlo:

contains aList anItem
echo $? # 0: match, 1: failed
chemila
fuente
37
debería ser[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov
12
Puede dar falso positivo si la entrada del usuario contiene caracteres especiales de expresión regular, por ejemplox=.
glenn jackman
44
Esto le dará ninguna falsos positivos / negativos: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
66
una solución más sucinta: [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. es correcto suponiendo que el espacio es el separador y $xno contiene espacio
Tianren Liu
2
Creo que es mejor usar una función "está en" isIn()para poder escribir primero el elemento en los parámetros. También se puede echoalgo en lugar de utilizar exitla siguiente manera: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Para que pueda utilizar la función de esta manera:result=$(isIn "-t" "-o -t 45") && echo $result
hayj
34

Matvey tiene razón, pero debe citar $ x y considerar cualquier tipo de "espacios" (por ejemplo, nueva línea) con

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

entonces, es decir

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

termina entonces

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

o

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

Tenga en cuenta que debe usar ""en caso de variables:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
Oriettaxx
fuente
3
Esta solución funciona incluso si $itemcontiene caracteres especiales, como .(pero la $listvariable probablemente necesita ser citada dentro de la prueba). Y la función se puede definir aún más simple: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
no funciona en casos como: list="aa bb xx cc ff"yx="aa bb"
creativeChips
Hola. Estoy tratando de aprender los fundamentos de bashscript y quería saber cómo podría analizar la cadena de la lista, en la lista, y hacer la verificación de existencia en esa lista. Puedo entender que te separaste usando el espacio, pero no pude entender completamente la expresión. ¿Me puede proporcionar las palabras clave para Google los fundamentos de esta expresión: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). O, si tiene tiempo, me gustaría escuchar su explicación para esta expresión. Nota: Supongo que es una expresión regex (comienza con ^ etc.) pero no sé qué significa = ~. Así que me encantaría una explicación general: P
Ali Yılmaz
28

La solución más fácil de mi humilde opinión es anteponer y agregar la cadena original con un espacio y verificar con una expresión regular con [[ ]]

haystack='foo bar'
needle='bar'

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

esto no será falso positivo en valores con valores que contienen la aguja como una subcadena, por ejemplo, con un pajar foo barbaz .

(El concepto es descaradamente robado del hasClass()método de JQuery )

blaimi
fuente
44
Si el separador no es espacio, la solución es más obvia. También se puede hacer sin regex: haystack="foo:bar"y[[ ":$haystack:" = *:$needle:* ]]
phiphi
25

qué tal si

echo $list | grep -w $x

puede verificar la salida o la $?línea anterior para tomar la decisión.

grep -w comprueba patrones de palabras enteras.

Kent
fuente
1
¿No validaría esto "val" si el "valor" es válido (es decir, en la lista) ya que es su subcadena?
Ofir Farchy
Sí, como lo haría la respuesta aceptada. @glennjackman ofrece una solución de trabajo.
f.ardelian
3
puedes usar grep -q para hacer que grep esté en silencio
Amir
esto también aceptaría coincidencias parciales, que pueden no ser lo que el usuario quiere
carnicer
3
@carnicer luego solo usa grep -w $ x para tener una coincidencia exacta
user1297406
18

También puede usar (* comodines) fuera de una declaración de caso, si usa corchetes dobles:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi
Mithun Sasidharan
fuente
3
Simple y elegante, +1
João Pereira
14
Esto dará falso positivo si "My" es una subcadena de otra cadena, por ejemplo, string = 'MyCommand string'.
Luke Lee
Vale la pena señalar que los comodines ( *My*) deben estar en el lado derecho de la prueba.
Jacob Vanus
8

Si su lista de valores debe estar codificada en la secuencia de comandos, es bastante fácil de probar usando case. Aquí hay un breve ejemplo, que puede adaptar a sus necesidades:

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

Si la lista es una variable de matriz en tiempo de ejecución, una de las otras respuestas probablemente sea mejor.

Toby Speight
fuente
7

Si la lista está arreglada en el script, me gusta lo siguiente mejor:

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

Luego, use validate "$x"para probar si $xestá permitido.

Si desea una línea y no le importa el espacio en blanco en los nombres de los elementos, puede usar esto (aviso en -wlugar de -x):

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

Notas:

  • Esto es compatible shcon POSIX .
  • validateno no aceptar subcadenas (quitar la -xopción de grep si quieres que).
  • validateinterpreta su argumento como una cadena fija, no como una expresión regular (elimine la -Fopción grep si lo desea).

Código de muestra para ejercer la función:

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

Considere explotar las claves de las matrices asociativas . Supongo que esto supera tanto la coincidencia de expresiones regulares / patrones como el bucle, aunque no lo he perfilado.

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

Salida:

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 )
Ruby Martes DONO
fuente
2
... y se puede simplificar el que el reemplazo (y no depender de echo -n) con el uso creativo de los parámetros: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight
¿Hay una manera fácil de crear tal matriz si solo tengo una lista (larga) como `list =" one two three xyz ... "?
Ott Toomet
5

Creo que es más fácil usar el formulario echo $LIST | xargs -n1 echo | grep $VALUEcomo se ilustra a continuación:

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

Esto funciona para una lista separada por espacios, pero puede adaptarla a cualquier otro delimitador (como :) haciendo lo siguiente:

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

Tenga en cuenta que "son necesarios para que la prueba funcione.

Sébastien Pierre
fuente
Esto hará LIST="SOMEITEM1 ITEM2"que se vuelva verdadero aunque ITEM1no esté en él
Ofir Farchy
Buena captura, actualicé el ejemplo con grep -e para excluir la coincidencia parcial.
Sébastien Pierre
Creo que hay un `extra al final de la declaración" if ". La forma correcta es: [-n "` echo $ LIST | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak
3

Pensé en agregar mi solución a la lista.

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

Ejemplos

$ 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

en lista

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

Suponiendo que la variable TARGET puede ser solo 'binomial' o 'regresión', lo siguiente sería suficiente:

# 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

Puede agregar más cadenas a la lista separándolas con un | (pipa) personaje.

La ventaja de utilizar egrep es que puede agregar fácilmente mayúsculas y minúsculas (-i) o verificar escenarios más complejos con una expresión regular.

Tagar
fuente
1

Esta es casi su propuesta original, pero casi una línea. No es tan complicado como otras respuestas válidas, y no tan dependiendo de las versiones de bash (puede funcionar con bashes antiguos).

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

Supongo que mi propuesta aún se puede mejorar, tanto en longitud como en estilo.

carnicero
fuente
0

Si no es demasiado largo; simplemente puede encadenarlos entre la igualdad a lo largo de una comparación OR lógica como tal.

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

Tuve este problema exacto y aunque lo anterior es feo, es más obvio lo que está sucediendo que las otras soluciones generalizadas.

Kelly Trinh
fuente