En Bash, ¿cómo puedo verificar si una cadena comienza con algún valor?

745

Me gustaría comprobar si una cadena comienza con "nodo", por ejemplo, "nodo001". Algo como

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

¿Cómo puedo hacerlo correctamente?


Además, necesito combinar expresiones para verificar si HOST es "user1" o comienza con "node"

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

> > > -bash: [: too many arguments

¿Cómo puedo hacerlo correctamente?

Tim
fuente
77
No se sienta demasiado tentado a combinar expresiones. Puede parecer más feo tener dos condicionales separados, aunque puede dar mejores mensajes de error y hacer que su script sea más fácil de depurar. También evitaría las características de bash. El cambio es el camino a seguir.
hendry

Respuestas:

1073

Este fragmento de la Guía avanzada de secuencias de comandos Bash dice:

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

Entonces lo tenías casi correcto; necesitabas corchetes dobles , no corchetes individuales.


Con respecto a su segunda pregunta, puede escribirla de esta manera:

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

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

Que hará eco

yes1
yes2

ifEs difícil acostumbrarse a la sintaxis de Bash (IMO).

Mark Rushakoff
fuente
12
Para expresiones regulares, ¿te refieres a [[$ a = ~ ^ z. *]]?
JStrahl
3
Entonces, ¿hay una diferencia funcional entre [[ $a == z* ]]y [[ $a == "z*" ]]? En otras palabras: ¿funcionan de manera diferente? ¿Y a qué te refieres específicamente cuando dices "$ a es igual a z *"?
Niels Bom
55
No necesita el separador de enunciado ";" si pones "entonces" en su propia línea
Yaza
66
Solo para completar: Para verificar si una cadena TERMINA con ...:[[ $a == *com ]]
lepe
44
El ABS es una elección desafortunada de referencias: es en gran medida el W3Schools of bash, lleno de contenido obsoleto y ejemplos de malas prácticas; El canal freenode #bash ha estado tratando de desalentar su uso al menos desde 2008 . ¿Alguna posibilidad de volver a nombrar en BashFAQ # 31 ? (También habría sugerido el wiki de Bash-Hackers, pero ha estado inactivo por un tiempo ahora).
Charles Duffy
207

Si está utilizando una versión reciente de Bash (v3 +), sugiero el operador de comparación de expresiones regulares de Bash =~, por ejemplo,

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

Para coincidir this or thaten una expresión regular, use |, por ejemplo,

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

Nota: esta es la sintaxis de expresión regular 'adecuada'.

  • user*significa usey cero o más ocurrencias de r, entonces usey userrrrcoincidirá.
  • user.*significa usery cero o más ocurrencias de cualquier carácter, por lo tanto user1, userXcoincidirán.
  • ^user.*significa hacer coincidir el patrón user.*al comienzo de $ HOST.

Si no está familiarizado con la sintaxis de expresiones regulares, intente consultar este recurso .

brabster
fuente
Gracias Brabster! Agregué a la publicación original una nueva pregunta sobre cómo combinar expresiones en if cluase.
Tim
2
Es una pena que la respuesta aceptada no diga nada sobre la sintaxis de la expresión regular.
CarlosRos
20
Para su información, el =~operador Bash solo hace coincidencia de expresiones regulares cuando el lado derecho está sin comillas. Si cita el lado derecho "Cualquier parte del patrón puede ser citada para forzar que coincida como una cadena". (1.) asegúrese de poner siempre las expresiones regulares a la derecha sin comillas y (2.) si almacena su expresión regular en una variable, asegúrese de NO citar el lado derecho cuando realice la expansión de parámetros.
Trevor Boyd Smith
145

Siempre trato de mantener POSIX en shlugar de usar extensiones Bash, ya que uno de los puntos principales de la secuencia de comandos es la portabilidad (además de conectar programas, no reemplazarlos).

En sh, hay una manera fácil de verificar si hay una condición "es-prefijo".

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

Dada la antigüedad, sh arcano y crujiente (y Bash no es la cura: es más complicado, menos consistente y menos portátil), me gustaría señalar un aspecto funcional muy agradable: mientras que algunos elementos de sintaxis como caseestán incorporados , las construcciones resultantes no son diferentes a cualquier otro trabajo. Se pueden componer de la misma manera:

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

O incluso más corto

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

O incluso más corto (solo para presentarlo !como un elemento del lenguaje, pero este es un mal estilo ahora)

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

Si te gusta ser explícito, crea tu propio elemento de lenguaje:

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

¿No es esto realmente bastante agradable?

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

Y dado shque básicamente son solo trabajos y listas de cadenas (y procesos internos, de los cuales se componen los trabajos), ahora podemos incluso hacer una programación funcional ligera:

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

Esto es elegante No es que yo recomendaría usar shalgo serio: se rompe demasiado rápido según los requisitos del mundo real (no hay lambdas, por lo que debemos usar cadenas. Pero anidar llamadas de función con cadenas no es posible, las tuberías no son posibles, etc.)

Jo So
fuente
12
+1 No solo es portátil, también es legible, idiomático y elegante (para script de shell). También se extiende naturalmente a múltiples patrones; case $HOST in user01 | node* ) ...
tripleee
¿Hay un nombre para este tipo de formato de código? if case $HOST in node*) true;; *) false;; esac; then Lo he visto aquí y allá, a mi parecer, parece un poco arrugado.
Niels Bom
@NielsBom No sé exactamente a qué te refieres con formateo, pero mi punto era que el código de shell es muy componible . Los casecomandos se convierten en comandos, pueden entrar if ... then.
Jo So
Ni siquiera veo por qué es componible, no entiendo suficiente script de shell para eso :-) Mi pregunta es sobre cómo este código usa paréntesis no coincidentes y punto y coma doble. No se parece en nada al script de shell que he visto antes, pero es posible que esté acostumbrado a ver el script bash más que el script sh, por lo que podría ser eso.
Niels Bom
Nota: Debería ser de beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }otra manera si $1tiene un literal *o ?podría dar una respuesta incorrecta.
LLFourn
80

Puede seleccionar solo la parte de la cadena que desea verificar:

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

Para su pregunta de seguimiento, puede usar un OR :

if [[ "$HOST" == user1 || "$HOST" == node* ]]
Martin Clayton
fuente
8
Deberías hacer una cita doble${HOST:0:4}
Jo So
@Jo So: ¿Cuál es la razón?
Peter Mortensen
@PeterMortensen, pruebaHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So
60

Prefiero los otros métodos ya publicados, pero a algunas personas les gusta usar:

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

Editar:

He agregado sus alternativas a la declaración de caso anterior

En su versión editada tiene demasiados corchetes. Debe tener un aspecto como este:

if [[ $HOST == user1 || $HOST == node* ]];
Pausado hasta nuevo aviso.
fuente
Gracias Dennis! Agregué a la publicación original una nueva pregunta sobre cómo combinar expresiones en if cluase.
Tim
11
"A algunas personas les gusta ...": este es más portátil en todas las versiones y shells.
carlosayam
Con las declaraciones de casos, puede dejar de lado las comillas alrededor de la variable, ya que no se divide la palabra. Sé que es inútil e inconsistente, pero me gusta dejar de lado las citas para que sea localmente más atractivo visualmente.
Jo So
Y en mi caso, tuve que dejar de lado las comillas antes): "/ *") no funcionó, / *) sí. (Estoy buscando cadenas que comiencen con /, es decir, rutas absolutas)
Josiah Yoder
36

Si bien la mayoría de las respuestas aquí son bastante correctas, muchas de ellas contienen Bashismos innecesarios. La expansión de parámetros POSIX le brinda todo lo que necesita:

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

y

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

${var#expr}tiras el más pequeño emparejamiento de prefijo exprde ${var}y rendimientos. Por tanto, si ${host}no no comenzar con user( node), ${host#user}( ${host#node}) es el mismo que ${host}.

exprpermite fnmatch()comodines, por lo tanto ${host#node??}y amigos también funcionan.

dhke
fuente
2
Yo diría que el bashism [[ $host == user* ]]podría ser necesario, ya que es mucho más legible que [ "${host#user}" != "${host}" ]. Como tal, dado que usted controla el entorno donde se ejecuta el script (apunte a las últimas versiones de bash), es preferible el primero.
x-yuri
2
@ x-yuri Francamente, simplemente empacaría esto en una has_prefix()función y nunca lo volvería a ver.
Dhke
30

Ya que # tiene un significado en Bash, llegué a la siguiente solución.

Además, me gusta más empacar cadenas con "" para superar espacios, etc.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi
ozma
fuente
Esto era exactamente lo que necesitaba. ¡Gracias!
Ionică Bizău
gracias, también me preguntaba cómo hacer coincidir una cadena blah:, ¡parece que esta es la respuesta!
Anentropic
12

Agregando un poco más de detalle de sintaxis a la respuesta de rango más alto de Mark Rushakoff.

La expresion

$HOST == node*

También se puede escribir como

$HOST == "node"*

El efecto es el mismo. Solo asegúrese de que el comodín esté fuera del texto citado. Si el comodín está dentro de las comillas, se interpretará literalmente (es decir, no como un comodín).

Señor Cara de Papa
fuente
8

@OP, para ambas preguntas puede usar case / esac:

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

Segunda pregunta

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

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

O Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac
ghostdog74
fuente
Tenga en cuenta que ;&solo está disponible en Bash> = 4.
pausa hasta nuevo aviso.
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

no funciona, porque todos [, [[y testreconocer la misma gramática recursiva. Consulte la sección EXPRESIONES CONDICIONALES en su página de manual de Bash.

Como un aparte, el SUSv3 dice

El comando condicional derivado de KornShell (paréntesis doble [[]] ) se eliminó de la descripción del lenguaje de comando de shell en una propuesta inicial. Se plantearon objeciones de que el problema real es el mal uso del comando de prueba ( [ ), y ponerlo en el shell es la forma incorrecta de solucionar el problema. En cambio, la documentación adecuada y una nueva palabra reservada de shell ( ! ) Son suficientes.

Las pruebas que requieren múltiples de ensayo operaciones se pueden hacer en el nivel de la cáscara utilizando invocaciones individuales de las pruebas Logicals de mando y de la cáscara, en vez de utilizar la propenso a errores -o bandera de prueba .

Tendría que escribirlo de esta manera, pero la prueba no lo admite:

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

prueba utiliza = para la igualdad de cadena, y lo más importante, no admite la coincidencia de patrones.

case/ esactiene buen soporte para la coincidencia de patrones:

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

Tiene el beneficio adicional de que no depende de Bash, y la sintaxis es portátil. De la especificación Single Unix , el lenguaje de comandos de Shell :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac
solo alguien
fuente
1
[y testson Bash incorporados, así como programas externos. Tratar type -a [.
Pausado hasta nuevo aviso.
Muchas gracias por explicar los problemas con el "compuesto o", simplemente alguien, ¡estaba buscando precisamente algo así! ¡Salud! Nota PS (no relacionada con OP): if [ -z $aa -or -z $bb ]; ... da " bash: [: -or: operador binario esperado "; Sin embargo if [ -z "$aa" -o -z "$bb" ] ; ...pasa.
sdaau
2

grep

Olvidando el rendimiento, esto es POSIX y se ve mejor que las casesoluciones:

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

Explicación:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2

Ajusté la respuesta de @ markrushakoff para que sea una función invocable:

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

Úselo así:

$ 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

Aquí hay una versión más compleja que proporciona un valor predeterminado especificado:

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

Úselo así:

$ 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
fuente
-5

Otra cosa que puedes hacer es catdescubrir qué es lo que estás haciendo eco yinline cut -c 1-1

Richard Tang
fuente