Shell: Verificando si una cadena contiene un carácter específico [duplicado]

Dec 22 2020
#!/bin/sh

TMP=$(cd Folder && ls) for name in $TMP; do

  if [[ "${name}" != *"a"* -a ${name} == *"b"* ]] ;then
   echo $name
  fi

done

Estaba tratando de generar los nombres que no tienen 'a' sino 'b' en su lugar. Este es el error que obtuve:

./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found

Qué estoy haciendo mal aquí ?

Respuestas

5 Kusalananda Dec 22 2020 at 02:19

Su principal problema es que está utilizando una coincidencia de patrones [[ ... ]]en un script ejecutado por el /bin/shcual no admite este tipo de pruebas. Consulte también la pregunta titulada El script Shell arroja un error no encontrado cuando se ejecuta desde un archivo sh. Pero si se ingresan manualmente, los comandos funcionan

El otro problema es que está combinando la salida de lsen una sola cadena antes de dividirla en palabras en espacios, tabuladores y nuevas líneas, y aplicar el nombre de archivo a las palabras generadas. Luego recorre el resultado de esto.

En lugar:

#!/bin/sh

cd Folder || exit 1

for name in *b*; do
    [ -e "$name" ] || [ -L "$name" ] || continue
    case $name in (*a*) ;; (*) printf '%s\n' "$name"; esac
done

Esto recorre todos los archivos en el Folderdirectorio que tienen la letra ben ellos y luego imprime los que no contienen un a. La prueba para el apersonaje se realiza usando case ... esac. El *a*patrón coincide con un acarácter en el nombre del archivo si lo hay, en cuyo caso no sucede nada. Las printfsentencias están en el "caso predeterminado" que se ejecuta si no hay ninguna aen la cadena.

Las pruebas con -ey -Lestán ahí para asegurarnos de que detectamos el caso de borde donde el patrón de bucle no coincide con nada. En ese caso, el patrón permanecerá sin expandir, por lo que para asegurarnos de que podemos distinguir entre un patrón sin expandir y un archivo existente real, probamos con -e. La -Lprueba consiste en detectar el caso de borde adicional de un enlace simbólico que tiene el mismo nombre que el patrón de bucle, pero que no apunta a ningún lugar válido. El bashsiguiente código no necesita esto porque usa la nullglobopción de shell en su lugar (no disponible en /bin/sh).

Ejecutar el bucle sobre la expansión del *b*patrón en lugar de cualquier lssalida tiene la ventaja de que esto también puede tratar con nombres de archivo que contienen espacios, pestañas, líneas nuevas y caracteres globales de nombre de archivo (los nombres de archivo nunca se colocan en una sola cadena que luego intentamos iterar).

En el bashshell, podría hacer lo mismo con una [[ ... ]]prueba de coincidencia de patrones, como intentó hacer usted mismo:

#!/bin/bash

cd Folder || exit 1

shopt -s nullglob

for name in *b*; do
    [[ $name != *a* ]] && printf '%s\n' "$name"
done

Ver también:

  • ¿Por qué mi script de shell se ahoga con espacios en blanco u otros caracteres especiales?
  • ¿Cuándo es necesaria la doble cotización?
  • ¿Por qué * no * analizar `ls` (y qué hacer en su lugar)?
  • ¿Por qué printf es mejor que echo?
6 StéphaneChazelas Dec 22 2020 at 02:35

Para imprimir los nombres de archivo Folderque contienen by no a, en zshy su operador glob ~( excepto / y no ):

#! /bin/zsh -
set -o extendedglob
print -rC1 -- Folder/(*b*~*a*)(N:t)

(elimine el N(para nullglob, si prefiere que se informe un error donde no hay un archivo coincidente).

En ksh93(el shell que introdujo ese [[...]]operador (que no es parte de la shsintaxis estándar ) y sus operadores &( y ) y !(...)( no ):

#! /bin/ksh93 -
(
  CDPATH= cd Folder &&
    set -- ~(N)@(*b*&!(*a*)) &&
    (($# > 0)) && printf '%s\n' "$@"
)

Con el bashshell (que like zshtambién ha copiado el [[...]]operador de ksh ), usando extglobpara admitir operadores glob ksh88 y nullglobpara obtener el efecto del Ncalificador glob de zsh o ksh93del ~(N)operador glob y alguna doble negación con |( o ) para lograr y :

#! /bin/bash -
(
  shopt -s extglob nullglob
  cd ./Folder &&
    set -- !(!(*b*)|*a*) &&
    (($# > 0)) && printf '%s\n' "$@"
)

En shsintaxis estándar :

#! /bin/sh -
(
  CDPATH= cd Folder || exit
  set -- [*]b[*] *b*
  [ "$1/$2" = '[*]b[*]/*b*' ] && exit # detect the case where the pattern
                                      # expands to itself because there's
                                      # no match as there's no nullglob
                                      # equivalent in sh
  shift
  for i do
    case $i in (*a*) ;; (*) set -- "$@" "$i" esac shift done [ "$#" -gt 0 ] && printf '%s\n' "$@"
)

Tenga en cuenta que las soluciones que utiliza cdno funcionarán si tiene acceso de lectura Folderpero no de búsqueda.

De manera más general, para responder a la pregunta general de cómo verificar si una cadena contiene otra sh, lo mejor es con la caseconstrucción, que es cómo se hace la coincidencia de patrones sh. Incluso podrías usar una función auxiliar:

contains()
  case "$1" in
    (*"$2"*) true;;
    (*) false;;
  esac

Para usar como si:

if contains foobar o; then
  echo foobar contains o
fi
if ! contains foobar z; then
  echo foobar does not contain o
fi
if
  contains "$file" b &&
    ! contains "$file" a then printf '%s\n' "$file contains b and not a "
fi