Capturer des groupes à partir d'une RegEx Grep
J'ai ce petit script dans sh
(Mac OSX 10.6) Pour regarder à travers un tableau de fichiers. Google a cessé d'être utile à ce stade:
files="*.jpg"
for f in $files
do
echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
name=$?
echo $name
done
Jusqu'à présent (évidemment, pour vous les gourous shell) $name
détient simplement 0, 1 ou 2, selon si grep
a trouvé que le nom de fichier correspondait à la matière fournie. ce que je voudrais, c'est capturer ce qui est à l'intérieur des parens ([a-z]+)
et le stocker dans une variable.
J'aimerais utiliser grep
seulement, si possible. Si non, veuillez ne pas Python ou Perl, etc. {[6] } ou quelque chose comme ça-je suis nouveau à shell et je voudrais attaquer cela sous l'angle puriste *nix.
Aussi, en tant que super-cool bonu s, je suis curieux de savoir comment je peux concaténer une chaîne dans shell? Le groupe, j'ai capturé était la chaîne "abc" stocké dans $nom, et je voulais ajouter la chaîne ".jpg" à la fin de celui-ci, pourrais-je cat $name '.jpg'
?
Veuillez expliquer ce qui se passe, si vous avez le temps.
7 réponses
Si vous utilisez Bash, vous n'avez même pas à utiliser grep
:
files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files
do
if [[ $f =~ $regex ]]
then
name="${BASH_REMATCH[1]}"
echo "${name}.jpg" # concatenate strings
name="${name}.jpg" # same thing stored in a variable
else
echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
fi
done
Il est préférable de mettre l'expression rationnelle dans une variable. Certains modèles ne fonctionneront pas s'ils sont inclus littéralement.
Cela utilise =~
qui est L'opérateur de correspondance regex de Bash. Les résultats de la correspondance sont enregistrés dans un tableau appelé $BASH_REMATCH
. Le premier groupe de capture est stocké dans l'index 1, le second (le cas échéant) dans l'index 2, etc. Index Zéro est la correspondance complète.
Vous devez être conscient que sans ancres, cette expression rationnelle (et celle l'utilisation de grep
) correspondra à l'un des exemples suivants et plus encore, ce qui peut ne pas être ce que vous cherchez:
123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz
Pour éliminer les deuxième et quatrième exemples, faites votre regex comme ceci:
^[0-9]+_([a-z]+)_[0-9a-z]*
, Qui dit que la chaîne doit démarrer, avec un ou plusieurs chiffres. Le carat représente le début de la chaîne. Si vous ajoutez un signe dollar à la fin de l'expression rationnelle, comme ceci:
^[0-9]+_([a-z]+)_[0-9a-z]*$
Alors le troisième exemple sera également éliminé puisque le point n'est pas parmi les les caractères dans l'expression rationnelle et le signe dollar représentent la fin de la chaîne. Notez que le quatrième exemple échoue également cette correspondance.
Si vous avez GNU grep
(environ 2.5 ou plus tard, je pense, quand l'opérateur \K
a été ajouté):
name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg
L'opérateur \K
(variable-length look-behind) fait correspondre le modèle précédent, mais n'inclut pas la correspondance dans le résultat. L'équivalent de longueur fixe est (?<=)
- le motif serait inclus avant la parenthèse de fermeture. Vous doit utiliser \K
si les quantificateurs peuvent correspondre à des chaînes de longueurs différentes (par exemple +
, *
, {2,4}
).
L'opérateur (?=)
correspond à des modèles de longueur fixe ou variable et est appelé "look-ahead". Il n'inclut pas non plus la chaîne correspondante dans le résultat.
Afin de rendre la correspondance insensible à la casse, l'opérateur (?i)
est utilisé. Il affecte les modèles qui le suivent de sorte que sa position est significative.
La regex peut avoir besoin d'être ajustée selon que d'autres caractères dans le nom de fichier. Vous noterez que dans ce cas, je montre un exemple de concaténation d'une chaîne en même temps que la sous-chaîne est capturée.
Ce n'est pas vraiment possible avec pure grep
, du moins pas généralement.
Mais si votre modèle est approprié, vous pouvez utiliser grep
plusieurs fois dans un pipeline pour réduire d'abord votre ligne à un format connu, puis pour extraire juste le bit que vous voulez. (Bien que des outils comme cut
et sed
soient bien meilleurs à cela).
Supposons pour des raisons d'argument que votre modèle était un peu plus simple: [0-9]+_([a-z]+)_
Vous pouvez extraire ceci comme ceci:
echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'
Le premier grep
serait supprimez toutes les lignes qui ne correspondent pas à votre patern global, le second grep
(qui a --only-matching
spécifié) afficherait la partie alpha du nom. Cela ne fonctionne que parce que le motif est approprié: "portion alpha" est assez spécifique pour sortir ce que vous voulez.
(mis à part: personnellement, j'utiliserais grep
+ cut
pour réaliser ce que vous recherchez: echo $name | grep {pattern} | cut -d _ -f 2
. Cela permet à cut
d'analyser la ligne en champs en divisant sur le délimiteur _
, et renvoie uniquement le Champ 2 (les numéros de champ commencent à 1)).
La philosophie Unix est d'avoir des outils qui font une chose, et le font bien, et les combinent pour réaliser des tâches non triviales, donc je dirais que grep
+ sed
etc est une façon plus Unixy de faire les choses: -)
Je me rends compte qu'une réponse a déjà été acceptée pour cela, mais d'un "angle puriste strictement * nix", il semble que le bon outil pour le travail est pcregrep
, ce qui ne semble pas encore avoir été mentionné. Essayez de changer les lignes:
echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
name=$?
À ce qui suit:
name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')
Pour obtenir uniquement le contenu du groupe de capture 1.
Le pcregrep
l'outil utilise la même syntaxe que vous avez déjà utilisé avec grep
, mais implémente la fonctionnalité vous avez besoin d'.
Le paramètre -o
fonctionne comme la version grep
si elle est nue, mais elle accepte également un paramètre numérique dans pcregrep
, qui indique le groupe de capture que vous souhaitez afficher.
Avec cette solution, il y a un strict minimum de changement requis dans le script. Vous remplacez simplement un utilitaire modulaire par un autre et modifiez les paramètres.
Note intéressante: vous pouvez utiliser plusieurs arguments-o Pour renvoyer plusieurs groupes de capture dans l'ordre dans lequel ils apparaissent sur la ligne.
Pas possible dans juste grep je crois
Pour sed:
name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`
Je vais prendre un coup de couteau au bonus si:
echo "$name.jpg"
C'est une solution qui utilise gawk. C'est quelque chose que je trouve que je dois utiliser souvent alors j'ai créé une fonction pour cela
function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }
Pour utiliser juste faire
$ echo 'hello world' | regex1 'hello\s(.*)'
world
Une suggestion pour vous - vous pouvez utiliser l'expansion des paramètres pour supprimer la partie du nom à partir du dernier trait de soulignement, et de même au début:
f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}
Alors {[1] } aura la valeur abc
.
Voir Apple developer docs , recherche en avant pour 'parameter Expansion'.
Si vous avez bash, vous pouvez utiliser étendue d'expansion
shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
IFS="_"
set -- $file
echo "This is your captured output : $2"
done
Ou
ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
IFS="_"
set -- $file
echo "This is your captured output : $2"
done