Greifen einer festen Zeichenfolge am Anfang einer Zeile

20

grep "^$1"Art von Arbeiten, aber wie kann ich entkommen, "$1"damit grep keine Zeichen darin speziell interpretiert?

Oder gibt es einen besseren Weg?

Bearbeiten: Ich möchte nicht suchen, '^$1'sondern nach einer dynamisch eingefügten festen Zeichenfolge, die nur dann gefunden werden soll, wenn sie am Anfang einer Zeile steht. Das habe ich mit gemeint $1.

PSkocik
quelle
Haben Sie versucht, einfache Anführungszeichen anstelle von doppelten Anführungszeichen zu verwenden, z grep '^$1'. Oder meintest du nicht, dass du verhindern willst, dass das $1durch die Shell erweitert wird?
11.
@mnille Ich möchte nicht nach '^ $ 1' suchen, sondern nach einem dynamisch eingefügten festen String, der nur dann gefunden werden sollte, wenn er am Anfang einer Zeile steht. Das habe ich mit der 1 $ gemeint.
PSkocik
3
Du kannst es auch mit machen, grepaber du musst zuerst jedes Sonderzeichen in deiner Zeichenkette printf %s ^;printf %s "$1" | sed 's/[][\.*^$]/\\&/g'; } | grep -f- infile
umgehen,
@don_crissti das ist besser als einige der anderen Antworten. Möchtest du es zu einem machen?
Roaima
@roaima - Ich weiß, aber es gibt hier bereits eine Reihe von Antworten, und dies ist etwas, was ich (und ein paar andere Benutzer hier) seit geraumer Zeit nach Hause gehämmert habe Wenn Sie es wünschen, können Sie es zu Ihrer Antwort hinzufügen, und ich werde den Kommentar hier entfernen (vergessen Sie nicht, die fehlende Klammer einzufügen).
don_crissti

Antworten:

7

Ich kann mir keine Möglichkeit vorstellen, dies mit zu tun grep. ^selbst ist Teil eines regulären Ausdrucks, sodass für die Verwendung regulärer Ausdrücke eine Interpretation erforderlich ist. Es ist trivial mit Teilzeichenanpassung in awk, perloder was auch immer:

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

Um mit Suchzeichenfolgen umzugehen, die Folgendes enthalten \, können Sie denselben Trick anwenden wie in der Antwort von 123 :

search="$1" awk 'substr($0, 1, length(ENVIRON["search"])) == ENVIRON["search"] { print }'
Stephen Kitt
quelle
Dies funktioniert nicht für Zeichenfolgen wie\/
123
@ 123 in der Tat habe ich eine Variante hinzugefügt, um damit umzugehen.
Stephen Kitt
Scheitert immer noch an komplizierten Zeichenfolgen, wie \\\/\/\/\\\\/sie \\///\\/im Programm zu sehen sind. Soweit mir bekannt ist, gibt es keine Möglichkeit, Backslashes in awk ordnungsgemäß zu umgehen, es sei denn, Sie wissen, wie viele davon im Voraus verwendet werden.
123
1
@ 123 danke, ich habe deinen Trick, durch die Umgebung zu gehen, angepasst, um eine Fluchtverarbeitung zu vermeiden.
Stephen Kitt
Diese Lösung gefällt mir immer noch am besten. Effizient (awk + keine Zeitverschwendung beim Herumschauen), schnelles Starten (awk + keine zusätzlichen Prozesse zum Einrichten des Status erforderlich) verwendet Standardtools und ist recht übersichtlich. Allen anderen Antworten fehlen zumindest einige davon. (Effizienz ist hier eine Stärke, da grep für unübertroffene Geschwindigkeit bekannt ist.)
PSkocik
14

Wenn Sie nur überprüfen müssen, ob eine Übereinstimmung gefunden wurde, schneiden Sie alle Eingabezeilen auf die Länge des gewünschten Präfixes ( $1) und verwenden Sie dann grep mit festem Muster:

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

Es ist auch einfach, die Anzahl der übereinstimmenden Zeilen zu ermitteln:

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

Oder die Zeilennummern aller übereinstimmenden Zeilen (Zeilennummern beginnen bei 1):

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

Sie könnten die Zeilennummern in headund taileingeben, um den vollständigen Text der übereinstimmenden Zeilen zu erhalten, aber zu diesem Zeitpunkt ist es einfacher, nur nach einer modernen Skriptsprache wie Python oder Ruby zu greifen.

(Die obigen Beispiele gehen von Posix grep und cut aus. Sie gehen davon aus, dass die zu durchsuchende Datei von der Standardeingabe stammt, können aber leicht angepasst werden, um stattdessen einen Dateinamen zu übernehmen.)

Bearbeiten: Sie sollten auch sicherstellen, dass das Muster ( $1) keine Zeichenfolge mit der Länge Null ist. Sonst cutscheitert das Sprichwort values may not include zero. Wenn Sie Bash verwenden set -o pipefail, können Sie auch Fehlerausgänge mit abfangen cut.

Lassi
quelle
10

Ein Weg mit Perl, der Backslashes respektiert

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

Dies setzt die Umgebungsvariable v für den Befehl und gibt dann aus, wenn der Index der Variablen 0 ist, dh der Anfang der Zeile.

Sie können das auch in awk machen

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

Hier ist eine All-Bash-Option, die ich nicht für die Textverarbeitung empfehle, aber sie funktioniert.

#!/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

Das Skript berechnet die Länge lendes eingegebenen Parameters $ 1 und verwendet dann die Parametererweiterung in jeder Zeile, um festzustellen, ob die ersten lenZeichen mit $ 1 übereinstimmen. In diesem Fall wird die Zeile gedruckt.

Jeff Schaller
quelle
4

Wenn Sie $1reines ASCII-Format haben und grepdie -POption (PCRE zu aktivieren) haben, können Sie dies tun:

#!/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"

Die Idee hier ist, grep -Pdass reguläre Ausdrücke mit \xXXLiteralzeichen angeben können, wobei XXder hexadezimale ASCII-Wert dieses Zeichens ist. Das Zeichen wird buchstäblich abgeglichen, auch wenn es sich ansonsten um ein spezielles Regex-Zeichen handelt.

odwird verwendet, um den erwarteten Zeilenanfang in eine Liste von Hexadezimalwerten umzuwandeln, die dann aneinander gereiht werden und jeweils mit dem Präfix \xprintf versehen sind. ^Dieser Zeichenfolge wird dann der erforderliche reguläre Ausdruck vorangestellt.


Wenn es sich bei Ihrem $1Code um einen Unicode handelt, wird dies erheblich schwieriger, da keine 1: 1-Entsprechung von Zeichen zu hexadezimalen Bytes vorliegt, die von ausgegeben werden od.

Digitales Trauma
quelle
3

Als Filter:

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

Führen Sie eine oder mehrere Dateien aus:

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

Im Abschnitt "Zitieren von Metazeichen" der Perlre-Dokumentation wird Folgendes erläutert:

Zitieren von Metazeichen

Backslashed Metazeichen in Perl sind alphanumerische Zeichen, wie zum Beispiel \b, \w, \n. Im Gegensatz zu einigen anderen regulären Ausdruckssprachen gibt es keine Symbole mit umgekehrten Schrägstrichen, die nicht alphanumerisch sind. So etwas das aussieht \\, \(, \), \[, \], \{, oder \}wird immer als Literalzeichen interpretiert, kein metacharacter. Dies wurde früher in einer allgemeinen Redewendung verwendet, um die speziellen Bedeutungen von Metazeichen für reguläre Ausdrücke in einer Zeichenfolge, die Sie für ein Muster verwenden möchten, zu deaktivieren oder in Anführungszeichen zu setzen. Zitieren Sie einfach alle Nicht-Wort-Zeichen:

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

(Wenn use localefestgelegt, hängt dies vom aktuellen Gebietsschema ab.) Heutzutage ist es üblicher, die quotemetaFunktion oder die \Q Escape-Sequenz für Meta-Anführungszeichen zu verwenden, um die speziellen Bedeutungen aller Meta-Zeichen wie folgt zu deaktivieren:

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

Beachten Sie, dass eine Interpolation mit doppelten Anführungszeichen zu verwirrenden Ergebnissen führen kann , wenn Sie wörtliche Backslashes (solche, die nicht in interpolierten Variablen enthalten sind) zwischen \Qund \Eeinfügen. Wenn Sie wörtliche umgekehrte Schrägstriche verwenden müssen \Q...\E, lesen Sie in Perlop den Abschnitt „Ausführliche Informationen zum Parsen von Konstrukten in Anführungszeichen“ .

quotemetaund \Qsind vollständig in Quotemeta beschrieben .

Greg Bacon
quelle
3

Wenn Ihr grep die Option -P hat, was PCRE bedeutet , können Sie dies tun:

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

Wenden Sie sich an diese Frage , und lesen Sie ggf. das PCRE-Dokument .

Bruce
quelle
2

Wenn es ein Zeichen gibt, das Sie nicht verwenden, können Sie damit den Zeilenanfang markieren. Zum Beispiel $'\a'(ASCII 007). Es ist hässlich, aber es wird funktionieren:

{ 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

Wenn Sie die übereinstimmenden Zeilen nicht benötigen, können Sie die nachfolgenden Zeilen löschen sedund verwenden grep -qF. Aber es ist viel einfacher mit awk(oder perl) ...

Roaima
quelle
0

Wenn Sie in einer Datei ohne Schleife suchen möchten, können Sie Folgendes verwenden:
Schneiden Sie die Datei mit der Länge der Suchzeichenfolge aus

  cut -c1-${#1} < file

Suchen Sie nach festen Zeichenfolgen und Zeilenumbrüchen

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

Verwenden Sie die Zeilennummern für so etwas wie sed -n '3p;11p' file

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

Wenn Sie diese Zeilen löschen möchten, verwenden Sie

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