Shell: vérifier si une chaîne contient un caractère spécifié [dupliquer]
#!/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
Votre principal problème est que vous utilisez une correspondance de modèle [[ ... ]]
dans un script exécuté /bin/sh
qui 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 ls
en 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 Folder
répertoire qui contiennent la lettre b
, puis imprime ceux de ceux qui ne contiennent pas de fichier a
. Le test du a
personnage se fait en utilisant case ... esac
. Le *a*
modèle correspond à un a
caractère dans le nom de fichier s'il y en a un, auquel cas rien ne se passe. Les printf
instructions sont dans le "cas par défaut" qui est exécuté s'il n'y a aucun a
dans la chaîne.
Les tests avec -e
et -L
sont 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 -L
test 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 bash
code ci-dessous n'en a pas besoin car il utilise à la nullglob
place 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 ls
sortie 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 bash
shell, 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?
Pour imprimer les noms de fichiers dans Folder
qui contiennent b
et non a
, dans zsh
et 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 sh
syntaxe standard ) et ses opérateurs &
( et ) et !(...)
( non ):
#! /bin/ksh93 -
(
CDPATH= cd Folder &&
set -- ~(N)@(*b*&!(*a*)) &&
(($# > 0)) && printf '%s\n' "$@"
)
Avec le bash
shell (qui comme zsh
a également copié l' [[...]]
opérateur de ksh ), en utilisant extglob
pour supporter les opérateurs glob ksh88 et nullglob
pour obtenir l'effet du N
qualificatif glob de zsh ou ksh93
de 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 sh
syntaxe 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 cd
ne fonctionneront pas si vous avez un accès en lecture Folder
mais 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' case
utiliser 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