J'ai ce petit script dans sh
(Mac OSX 10.6) pour parcourir 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'ici (évidemment pour vous, gourous de Shell) $name
ne contient que 0, 1 ou 2, selon que _ grep
trouve que le nom du fichier correspond au contenu fourni. Ce que j'aimerais, c'est capturer le contenu des parenthèses ([a-z]+)
et le stocker dans une variable.
Je voudrais tiliser grep
seulement, si possible. Sinon, veuillez ne pas utiliser Python ou Perl, etc. sed
ou quelque chose du genre - je suis nouveau dans Shell et je voudrais attaquer cela sous l'angle puriste * nix.
En outre, en tant que super-cool bon s, je suis curieux de savoir comment je peux concaténer string dans une coque? Est-ce que le groupe que j'ai capturé était la chaîne "somename" stockée dans $ name, et je voulais ajouter la chaîne ".jpg" à la fin, pourrais-je cat $name '.jpg'
?
S'il vous plaît expliquer ce qui se passe, si vous avez le temps.
Si vous utilisez Bash, vous n'avez même pas besoin d'utiliser grep
:
files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files # unquoted in order to allow the glob to expand
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.
Ceci utilise =~
qui est l'opérateur de correspondance des expressions rationnelles 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 deuxième (le cas échéant) dans l'index 2, etc. L'indice zéro correspond à la correspondance complète.
Sachez que sans ancres, cette expression rationnelle (et celle utilisant grep
) correspondra à l'un des exemples suivants, et plus encore, ce qui peut ne pas correspondre à ce que vous recherchez:
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ébut 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 la regex, procédez comme suit:
^[0-9]+_([a-z]+)_[0-9a-z]*$
alors le troisième exemple sera également éliminé puisque le point ne fait pas partie des caractères de la regex et que le signe dollar représente 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, lorsque 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
(recherche de longueur variable) 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 fermante. Vous devez utiliser \K
si les quantificateurs peuvent correspondre à des chaînes de différentes longueurs (par exemple +
, *
, {2,4}
).
L'opérateur (?=)
correspond aux modèles de longueur fixe ou variable et s'appelle "anticipation". 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 schémas qui le suivent et sa position est donc significative.
Il peut être nécessaire d’ajuster l’expression rationnelle en fonction de la présence d’autres caractères dans le nom du fichier. Vous remarquerez 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.
Toutefois, si votre modèle convient, vous pourrez peut-être utiliser plusieurs fois grep
dans un pipeline pour réduire votre ligne à un format connu, puis pour extraire le bit souhaité. (Bien que des outils comme cut
et sed
soient bien meilleurs à cela).
Supposons, par souci d'argument, que votre modèle était un peu plus simple: [0-9]+_([a-z]+)_
Vous pouvez extraire ceci de la manière suivante:
echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'
Le premier grep
supprime toutes les lignes qui ne correspondent pas à votre motif général, le second grep
(qui a --only-matching
spécifié) affichera la partie alpha du nom. Cela ne fonctionne que parce que le motif est approprié: "partie alpha" est suffisamment spécifique pour extraire ce que vous voulez.
(À part: j'utiliserais personnellement grep
+ cut
pour obtenir ce que vous recherchez: echo $name | grep {pattern} | cut -d _ -f 2
. Ceci permet à cut
d'analyser la ligne en champs en divisant le délimiteur. _
, et ne renvoie que le champ 2 (les numéros de champ commencent par 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 manière plus Unixy de faire des choses :-)
Je me rends compte qu’une réponse était déjà acceptée pour cela, mais d’un "angle puriste strictement * nix", il semble que le bon outil pour le travail est pcregrep
, qui ne semble pas avoir encore é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.
L'outil pcregrep
utilise la même syntaxe que celle que vous avez déjà utilisée avec grep
, mais implémente les fonctionnalités dont vous avez besoin.
Le paramètre -o
fonctionne comme la version grep
s'il est nu, mais il accepte également un paramètre numérique dans pcregrep
qui indique quelle capture. groupe que vous voulez montrer.
Avec cette solution, un minimum de modifications est nécessaire dans le script. Vous remplacez simplement un utilitaire modulaire par un autre et modifiez les paramètres.
Remarque 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 tenter le bonus:
echo "$name.jpg"
C'est une solution qui utilise gawk. C'est quelque chose que j'ai besoin d'utiliser souvent, alors j'ai créé une fonction pour ça
function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }
utiliser juste faire
$ echo 'hello world' | regex1 'hello\s(.*)'
world
Une suggestion pour vous - vous pouvez utiliser le paramètre expand pour supprimer la partie du nom à partir du dernier trait de soulignement, et de la même manière au début:
f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}
Alors name
aura la valeur abc
.
Voir Apple documentation de développeur , recherchez "Expansion des paramètres".
si vous avez bash, vous pouvez utiliser le globbing étendu
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