grepping une chaîne fixe au début d'une ligne


20

grep "^$1"sorte de travaux, mais comment puis-je m'échapper "$1"pour que grep n'y interprète aucun caractère spécialement?

Ou existe-t-il une meilleure façon?

Edit: je ne veux pas rechercher '^$1'mais une chaîne fixe insérée dynamiquement qui ne doit être mise en correspondance que si elle se trouve au début d'une ligne. C'est ce que je voulais dire par $1.


Avez-vous essayé d'utiliser des guillemets simples au lieu de guillemets doubles, par exemple grep '^$1'? Ou ne vouliez-vous pas dire que vous vouliez empêcher l' $1expansion par le shell?
mnille

@mnille Je ne veux pas rechercher '^ $ 1' mais une chaîne fixe insérée dynamiquement qui ne doit être mise en correspondance que si elle se trouve au début d'une ligne. C'est ce que je voulais dire par le 1 $.
PSkocik

3
Vous pouvez aussi le faire avec, grepmais vous devrez d'abord échapper tout caractère spécial dans votre chaîne, par exempleprintf %s ^;printf %s "$1" | sed 's/[][\.*^$]/\\&/g'; } | grep -f- infile
don_crissti

@don_crissti c'est mieux que certaines des autres réponses. Vous voulez en faire un?
roaima

@roaima - Je sais, mais il y a déjà un tas de réponses ici et cela (échapper aux caractères spéciaux à l'intérieur de vars) est quelque chose que je (et quelques autres utilisateurs ici) martelent à la maison depuis un certain temps ... Vous pouvez toujours ajouter à votre réponse si vous le souhaitez et je supprimerai le commentaire ici (n'oubliez pas d'ajouter l'accolade principale manquante).
don_crissti

Réponses:


7

Je ne peux pas penser à un moyen de le faire en utilisant grep; ^lui-même fait partie d'une expression régulière, donc son utilisation nécessite l'interprétation des expressions régulières. C'est trivial d'utiliser la correspondance de sous-chaîne dans awk, perlou autre:

awk -v search="$1" 'substr($0, 1, length(search)) == search { print }'

Pour gérer les chaînes de recherche contenant \, vous pouvez utiliser la même astuce que dans la réponse de 123 :

search="$1" awk 'substr($0, 1, length(ENVIRON["search"])) == ENVIRON["search"] { print }'

Cela ne fonctionnera pas pour des chaînes telles que\/
123

@ 123 en effet, j'ai ajouté une variante pour gérer cela.
Stephen Kitt

Échouera toujours pour des chaînes complexes telles que \\\/\/\/\\\\/celles vues \\///\\/dans le programme. Pour autant que je sache, il n'y a aucun moyen d'échapper correctement aux barres obliques inverses dans awk, sauf si vous savez combien seront utilisées à l'avance.
123

1
@ 123 merci, j'ai adapté votre astuce pour parcourir l'environnement pour éviter le traitement d'échappement.
Stephen Kitt

J'aime toujours mieux cette solution. Efficace (awk + pas de temps perdu à regarder autour), le démarrage rapide (awk + pas de processus supplémentaires nécessaires pour configurer l'état) utilise des outils standard et est assez concis. Toutes les autres réponses manquent au moins de certaines d'entre elles. (L'efficacité est un point fort ici car grep est connu pour sa vitesse inégalée.)
PSkocik

14

Si vous avez seulement besoin de vérifier si une correspondance est trouvée ou non, coupez toutes les lignes d'entrée à la longueur du préfixe souhaité ( $1) puis utilisez grep à modèle fixe:

if cut -c 1-"${#1}" | grep -qF "$1"; then
    echo "found"
else
    echo "not found"
fi

Il est également facile d'obtenir le nombre de lignes correspondantes:

cut -c 1-"${#1}" | grep -cF "$1"

Ou les numéros de ligne de toutes les lignes correspondantes (les numéros de ligne commencent à 1):

cut -c 1-"${#1}" | grep -nF "$1" | cut -d : -f 1

Vous pouvez alimenter les numéros de ligne headet tailobtenir le texte intégral des lignes correspondantes, mais à ce stade, il est plus facile d'atteindre simplement un langage de script moderne comme Python ou Ruby.

(Les exemples ci-dessus supposent Posix grep et cut. Ils supposent que le fichier à rechercher provient d'une entrée standard, mais peut facilement être adapté pour prendre un nom de fichier à la place.)

Modifier: vous devez également vous assurer que le motif ( $1) n'est pas une chaîne de longueur nulle. Sinon, cutéchoue à dire values may not include zero. De plus, si vous utilisez Bash, utilisez set -o pipefailpour attraper les sorties d'erreur par cut.


10

Une façon d'utiliser perl qui respectera les antislashs

v="$1" perl -ne 'print if index($_, $ENV{"v"} )==0' file

Cela définit la variable d'environnement v pour la commande, puis imprime si l'index de la variable est 0, c'est-à-dire le début de la ligne.

Vous pouvez également faire identique dans awk

v="$1" awk 'index($0, ENVIRON["v"])==1' file

7

Voici une option tout-bash, pas que je recommande bash pour le traitement de texte, mais cela fonctionne.

#!/usr/bin/env bash
# searches for $1 at the beginning of the line of its input

len=${#1}
while IFS= read -r line
do
  [[ "${line:0:len}" = "$1" ]] && printf "%s\n" "$line"
done

Le script calcule la longueur lendu paramètre entré $ 1, puis utilise l'expansion des paramètres sur chaque ligne pour voir si les premiers lencaractères correspondent à $ 1. Si c'est le cas, il imprime la ligne.


4

Si votre $1est ASCII pur et que vous avez grepl' -Poption (pour activer PCRE), vous pouvez le faire:

#!/bin/bash

line_start="$1"
line_start_raw=$(printf '%s' "$line_start" | od -v -t x1 -An)
line_start_hex=$(printf '\\x%s' $line_start_raw)
grep -P "^$line_start_hex"

L'idée ici est de permettre grep -Paux expressions régulières \xXXde spécifier des caractères littéraux, oùXX est la valeur ASCII hexadécimale de ce caractère. Le caractère correspond littéralement, même s'il s'agit autrement d'un caractère regex spécial.

odest utilisé pour convertir le début de ligne attendu en une liste de valeurs hexadécimales, qui sont ensuite enchaînées, chacune préfixée \xpar printf. ^est ensuite ajouté cette chaîne pour construire l'expression régulière requise.


Si votre $1est unicode, cela devient un peu plus difficile, car il n'y a pas de correspondance 1: 1 de caractères avec des octets hexadécimaux en sortie par od.


3

En tant que filtre:

perl -ne 'BEGIN {$pat = shift} print if /^\Q$pat/' search-pattern

Exécuter sur un ou plusieurs fichiers:

perl -ne 'BEGIN {$pat = shift} print if /^\Q$pat/' search-pattern file..

La section «Citations de métacaractères» de la documentation de perlre explique:

Citant des métacaractères

Métacaractères backslashés en Perl sont alphanumériques, tels que \b, \w, \n. Contrairement à certains autres langages d'expression régulière, il n'y a pas de symboles avec barre oblique inverse qui ne soient pas alphanumériques. Donc , tout ce qui ressemble \\, \(, \), \[, \], \{ou \}est toujours interprété comme un caractère littéral, pas un métacaractère. Cela était autrefois utilisé dans un idiome commun pour désactiver ou citer les significations spéciales des métacaractères d'expression régulière dans une chaîne que vous souhaitez utiliser pour un modèle. Citez simplement tous les caractères non «verbaux»:

    $pattern =~ s/(\W)/\\$1/g;

(Si use localeest défini, cela dépend des paramètres régionaux actuels.) Aujourd'hui, il est plus courant d'utiliser la quotemetafonction ou la \Q séquence d'échappement de métaquotage pour désactiver toutes les significations spéciales de tous les métacaractères comme ceci:

    /$unquoted\Q$quoted\E$unquoted/

Sachez que si vous mettez des barres obliques inverses littérales (celles qui ne sont pas à l'intérieur des variables interpolées) entre \Qet \E, l'interpolation de barre oblique inverse entre guillemets doubles peut conduire à des résultats confus. Si vous devez utiliser des barres obliques inverses littérales à l'intérieur \Q...\E, consultez «Détails sanglants de l'analyse des constructions entre guillemets» dans perlop .

quotemetaet \Qsont entièrement décrits dans quotemeta .


3

Si votre grep a l'option -P, ce qui signifie PCRE , vous pouvez le faire:

grep -P "^\Q$1\E"

Reportez-vous à cette question et consultez la documentation PCRE pour plus de détails si vous le souhaitez.


2

S'il y a un caractère que vous n'utilisez pas, vous pouvez l'utiliser pour marquer le début de la ligne. Par exemple, $'\a'(ASCII 007). C'est moche mais ça va marcher:

{ echo 'this is a line to match'; echo 'but this is not'; } >file.txt

stuffing=$'\a'    # Guaranteed never to appear in your source text
required='this'   # What we want to match that beginning of a line

match=$(sed "s/^/$stuffing/" file.txt | grep -F "$stuffing$required" | sed "s/^$stuffing//")

if [[ -n "$match" ]]
then
    echo "Yay. We have a match: $match"
fi

Si vous n'avez pas besoin des lignes correspondantes, vous pouvez supprimer la fin sedet utiliser grep -qF. Mais c'est beaucoup plus facile avec awk(ou perl) ...


0

Lorsque vous souhaitez rechercher dans un fichier sans boucle, vous pouvez utiliser:
Couper le fichier avec la longueur de la chaîne de recherche

  cut -c1-${#1} < file

Recherchez des chaînes fixes et des numéros de ligne de retour

  grep -Fn "$1" <(cut -c1-${#1} < file)

Utilisez les numéros de ligne pour quelque chose comme sed -n '3p;11p' file

  sed -n "$(grep -Fn "$1" <(cut -c1-${#1} < file) | sed 's/:.*/p;/' | tr -d '\n')" file

Lorsque vous souhaitez supprimer ces lignes, utilisez

  sed "$(grep -Fn "$1" <(cut -c1-${#1} < file) | sed 's/:.*/d;/' | tr -d '\n')" file
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.