web-dev-qa-db-fra.com

Utilisation d'expressions régulières dans un script shell

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:

  • Quelle est la bonne façon d'analyser une chaîne en utilisant des expressions régulières dans un script shell Linux?
  • Est-ce que sed est la bonne chose à utiliser ici? 
  • Cela pourrait-il être fait en utilisant grep
  • Existe-t-il une autre commande plus facile/appropriée?
24
Amarghosh

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).

12
paxdiablo

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.

8
user181548

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
5
Török Gábor

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éponses
  • tr pour remplacer des caractères
  • cut, paste pour la gestion des entrées multicolonnes
  • bash avec sa riche syntaxe $(...) pour accéder aux variables
  • tail, head pour conserver les dernières ou premières lignes d'un fichier
3
mouviciel

sed 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.

2
Brian Agnew

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
2
qba

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
2
ghostdog74

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.

1
pavium

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)}'
0
Dennis Williamson

RegEx simple via Shell

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;

0
Beejor