Shell: vérifier si une chaîne contient un caractère spécifié [dupliquer]

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

J'essayais de sortir les noms qui n'ont pas de «a» mais de «b» à la place. Voici l'erreur que j'ai obtenue:

./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'est-ce que je fais de mal ici?

Réponses

5 Kusalananda Dec 22 2020 at 02:19

Votre principal problème est que vous utilisez une correspondance de modèle [[ ... ]]dans un script exécuté /bin/shqui ne prend pas en charge ces types de tests. Voir aussi la question intitulée Le script Shell renvoie une erreur introuvable lorsqu'il est exécuté à partir d'un fichier sh. Mais si elles sont entrées manuellement, les commandes fonctionnent

L'autre problème est que vous combinez la sortie de lsen une seule chaîne avant de la diviser en mots sur les espaces, les tabulations et les retours à la ligne, et d'appliquer une extension de nom de fichier aux mots générés. Vous bouclez ensuite sur le résultat de ceci.

Au lieu:

#!/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

Cela boucle sur tous les fichiers du Folderrépertoire qui contiennent la lettre b, puis imprime ceux de ceux qui ne contiennent pas de fichier a. Le test du apersonnage se fait en utilisant case ... esac. Le *a*modèle correspond à un acaractère dans le nom de fichier s'il y en a un, auquel cas rien ne se passe. Les printfinstructions sont dans le "cas par défaut" qui est exécuté s'il n'y a aucun adans la chaîne.

Les tests avec -eet -Lsont là pour nous assurer que nous attrapons le cas de bord où le motif de boucle ne correspond à rien. Dans ce cas, le modèle restera non développé, donc pour nous assurer que nous pouvons faire la distinction entre un modèle non développé et un fichier existant réel, nous testons avec -e. Le -Ltest consiste à attraper le cas d'arête supplémentaire d'un lien symbolique qui porte le même nom que le modèle de boucle, mais qui ne pointe vers aucun endroit valide. Le bashcode ci-dessous n'en a pas besoin car il utilise à la nullglobplace l'option shell (non disponible dans /bin/sh).

L'exécution de la boucle sur l'expansion du *b*modèle au lieu de toute lssortie a l'avantage de pouvoir également traiter les noms de fichiers contenant des espaces, des tabulations, des retours à la ligne et des caractères globuleux de nom de fichier (les noms de fichiers ne sont jamais placés dans une seule chaîne que nous essayons itérer dessus).

Dans le bashshell, vous pouvez faire la même chose avec un [[ ... ]]test de correspondance de modèle, comme vous avez essayé de le faire vous-même:

#!/bin/bash

cd Folder || exit 1

shopt -s nullglob

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

Voir également:

  • Pourquoi mon script shell s'étouffe-t-il avec des espaces ou d'autres caractères spéciaux?
  • Quand une double soumission est-elle nécessaire?
  • Pourquoi * pas * analyser `ls` (et que faire à la place)?
  • Pourquoi printf est-il meilleur que echo?
6 StéphaneChazelas Dec 22 2020 at 02:35

Pour imprimer les noms de fichiers dans Folderqui contiennent bet non a, dans zshet son ~( sauf / et-non ) opérateur glob:

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

(supprimez le N(pour nullglob, si vous préférez qu'une erreur soit signalée là où il n'y a pas de fichier correspondant).

Dans ksh93(le shell qui a introduit cet [[...]]opérateur (qui ne fait pas partie de la shsyntaxe standard ) et ses opérateurs &( et ) et !(...)( non ):

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

Avec le bashshell (qui comme zsha également copié l' [[...]]opérateur de ksh ), en utilisant extglobpour supporter les opérateurs glob ksh88 et nullglobpour obtenir l'effet du Nqualificatif glob de zsh ou ksh93de l' ~(N)opérateur glob de zsh et une double négation avec |( ou ) pour atteindre et :

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

Dans la shsyntaxe standard :

#! /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' "$@"
)

Notez que les solutions qui utilisent cdne fonctionneront pas si vous avez un accès en lecture Foldermais pas un accès de recherche.

Plus généralement, pour répondre à la question générale de savoir comment vérifier si une chaîne en contient une autre sh, le mieux est d' caseutiliser la construction qui correspond à la manière dont vous effectuez la correspondance de motifs dans sh. Vous pouvez même utiliser une fonction d'assistance:

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

A utiliser comme 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