Quelle est la bonne façon d'analyser une chaîne en utilisant des expressions régulières dans un script shell Linux? J'ai écrit le script suivant pour imprimer mon représentant SO sur la console à l'aide de curl
et sed
(pas uniquement parce que je suis fou de rep, car j'essaie d'apprendre des scripts Shell et des expressions régulières avant de passer à Linux).
json=$(curl -s http://stackoverflow.com/users/flair/165297.json)
echo $json | sed 's/.*"reputation":"\([0-9,]\{1,\}\)".*/\1/' | sed s/,//
Mais d’une certaine manière, j’estime que sed
n’est pas le bon outil à utiliser ici. J'ai entendu dire que grep
est tout au sujet de regex et l'exploré un peu. Mais apparemment, il affiche toute la ligne chaque fois qu'une correspondance est trouvée - j'essaie d'extraire un nombre d'une seule ligne de texte. Voici une version réduite de la chaîne sur laquelle je travaille (retournée par curl
).
{"displayName": "Amarghosh", "reputation": "2 737", "badgeHtml": "\ u003cspan title = \" 1 badge argenté\"\ u003e\u003cspan class = \" badge2\"\ u003e ●\u003c/span\u003e\u003cspan class =\"badgecount \"\u003e1\u003c/span\u003e\u003c/span\u003e "}
Je suppose que mes questions sont:
sed
est la bonne chose à utiliser ici? grep
? La commande grep
sélectionnera la ou les lignes souhaitées parmi plusieurs, mais elle ne manipulera pas directement la ligne. Pour cela, vous utilisez sed
dans un pipeline:
someCommand | grep 'Amarghosh' | sed -e 's/foo/bar/g'
Vous pouvez également utiliser awk
(ou Perl
si disponible). C’est un outil de traitement de texte bien plus puissant que sed
à mon avis.
someCommand | awk '/Amarghosh/ { do something }'
Pour des manipulations de texte simples, utilisez simplement le combo grep/sed
. Lorsque vous avez besoin d'un traitement plus compliqué, passez à awk
ou Perl
.
Ma première pensée est simplement d'utiliser:
echo '{"displayName":"Amarghosh","reputation":"2,737","badgeHtml"'
| sed -e 's/.*tion":"//' -e 's/".*//' -e 's/,//g'
qui conserve le nombre de processus sed
à un (vous pouvez donner plusieurs commandes avec -e
).
Vous voudrez peut-être utiliser Perl pour de telles tâches. En guise de démonstration, voici un script Perl qui affiche le numéro souhaité:
#!/usr/local/bin/Perl
use warnings;
use strict;
use LWP::Simple;
use JSON;
my $url = "http://stackoverflow.com/users/flair/165297.json";
my $flair = get ($url);
my $parsed = from_json ($flair);
print "$parsed->{reputation}\n";
Ce script nécessite l'installation du module JSON, à l'aide de la commande cpan JSON
.
Pour utiliser JSON dans un script Shell, utilisez jsawk which like awk, mais pour JSON.
json=$(curl -s http://stackoverflow.com/users/flair/165297.json)
echo $json | jsawk 'return this.reputation' # 2,747
Ma proposition:
$ echo $json | sed 's/,//g;s/^.*reputation...\([0-9]*\).*$/\1/'
Je mets deux commandes en argument sed:
s/,//g
est utilisé pour supprimer toutes les virgules, en particulier celles présentes dans la valeur de réputation.
s/^.*reputation...\([0-9]*\).*$/\1/
localise la valeur de réputation dans la ligne et remplace la ligne entière par cette valeur.
Dans ce cas particulier, je trouve que sed
fournit la commande la plus compacte sans perte de lisibilité.
Parmi les autres outils permettant de manipuler des chaînes (pas seulement regex), citons:
grep
, awk
, Perl
mentionné dans la plupart des autres réponsestr
pour remplacer des caractèrescut
, paste
pour la gestion des entrées multicolonnesbash
avec sa riche syntaxe $(...)
pour accéder aux variablestail
, head
pour conserver les dernières ou premières lignes d'un fichiersed
convient, mais vous créerez un nouveau processus pour chaque sed
que vous utiliserez (ce qui peut s'avérer trop lourd dans des scénarios plus complexes). grep
n'est pas vraiment approprié. C'est un outil de recherche qui utilise les expressions rationnelles pour trouver des lignes d'intérêt.
Perl est une solution appropriée ici, étant un langage de script Shell doté de puissantes fonctionnalités d’expression régulière. Il fera presque tout ce dont vous avez besoin sans générer de processus distincts (contrairement aux scripts Unix Shell normaux) et dispose d'une immense bibliothèque de fonctions supplémentaires.
Vous pouvez le faire avec grep. Il y a un commutateur -o dans l'extrait de sorcière grep, la chaîne correspondante ne correspond pas à la ligne entière.
$ echo $json | grep -o '"reputation":"[0-9,]\+"' | grep -o '[0-9,]\+'
2,747
1) Quelle est la bonne façon d’analyser une chaîne en utilisant des expressions régulières dans un script shell Linux?
Les outils qui incluent des capacités d’expression régulière incluent sed, grep, awk, Perl, Python, pour ne citer que quelques-uns. Même les nouvelles versions de Bash ont des capacités regex. Tout ce que vous avez à faire est de consulter la documentation pour savoir comment les utiliser.
2) Est-ce que sed est la bonne chose à utiliser ici?
Cela peut être, mais pas nécessaire.
3) Cela pourrait-il être fait en utilisant grep?
Oui il peut. vous ne ferez que construire un regex similaire à celui que vous utiliseriez avec sed ou d’autres. Notez que grep fait ce qu’il fait, et si vous voulez modifier des fichiers, il ne le fera pas pour vous.
4) Existe-t-il une autre commande plus facile/plus appropriée?
Bien sûr. regex peut être puissant, mais ce n'est pas nécessairement le meilleur outil à utiliser à chaque fois. Cela dépend aussi de ce que vous entendez par "plus facile/approprié". L’autre méthode à utiliser avec un minimum de tracas sur regex utilise l’approche champs/délimiteur. vous recherchez des modèles qui peuvent être "divisés". par exemple, dans votre cas (j'ai téléchargé le fichier 165297.json au lieu d'utiliser curl .. (mais c'est la même chose)
awk 'BEGIN{
FS="reputation" # split on the Word "reputation"
}
{
m=split($2,a,"\",\"") # field 2 will contain the value you want plus the rest
# Then split on ":" and save to array "a"
gsub(/[:\",]/,"",a[1]) # now, get rid of the redundant characters
print a[1]
}' 165297.json
sortie:
$ ./Shell.sh
2747
sed
est une commande parfaitement valide pour votre tâche, mais ce n'est peut-être pas la seule.
grep
peut être utile aussi, mais comme vous le dites, il affiche toute la ligne. C'est très utile pour filtrer les lignes d'un fichier multiligne et supprimer les lignes inutiles.
Les scripts Shell efficaces peuvent utiliser une combinaison de commandes (pas seulement les deux que vous avez mentionnées), exploitant les talents de chacune d’elles.
Aveuglément:
echo $json | awk -F\" '{print $8}'
Similaires (le séparateur de champ peut être une expression régulière):
awk -F'{"|":"|","|"}' '{print $5}'
Plus intelligent (recherchez la clé et imprimez sa valeur):
awk -F'{"|":"|","|"}' '{for(i=2; i<=NF; i+=2) if ($i == "reputation") print $(i+1)}'
En négligeant le code spécifique en question, il peut arriver que vous souhaitiez effectuer une regex rapide en remplaçant tout de stdin à stdout à l'aide de Shell, d'une manière simple, en utilisant une syntaxe de chaîne similaire à JavaScript.
Voici quelques exemples pour ceux qui cherchent un moyen de le faire. Perl est un meilleur pari sur Mac car il manque certaines options sed. Si vous voulez obtenir stdin en tant que variable, vous pouvez utiliser MY_VAR=$(cat);
.
echo 'text' | Perl -pe 's/search/replace/g'; # using Perl
echo 'text' | sed -e 's/search/replace/g'; # using sed
Et voici un exemple de fonction regex personnalisée et réutilisable. Les arguments sont les suivants: chaîne source (ou - pour stdin), recherche , remplacer et options .
regex() {
case "$#" in
( '0' ) exit 1 ;; ( '1' ) echo "$1"; exit 0 ;;
( '2' ) REP='' ;; ( '3' ) REP="$3"; OPT='' ;;
( * ) REP="$3"; OPT="$4" ;;
esac
TXT="$1"; SRCH="$2";
if [ "$1" = "--" ]; then [ ! -t 0 ] && read -r TXT; fi
echo "$TXT" | Perl -pe 's/'"$SRCH"'/'"$REP"'/'"$OPT";
}
echo 'text' | regex -- search replace g;