grepping stały ciąg na początku linii

20

grep "^$1"trochę działa, ale jak mam uciec, "$1"żeby grep nie interpretował w nim żadnych znaków?

Czy jest jakiś lepszy sposób?

Edycja: Nie chcę szukać, '^$1'ale dynamicznie wstawianego stałego ciągu, który powinien być dopasowany tylko, jeśli znajduje się na początku linii. Właśnie to miałem na myśli $1.

PSkocik
źródło
Czy próbowałeś użyć pojedynczych cudzysłowów zamiast podwójnych cudzysłowów, np. grep '^$1'? Czy nie miałeś na myśli, że chcesz zapobiec $1rozszerzaniu się powłoki?
mnille
@mnille Nie chcę szukać „^ $ 1”, ale dynamicznie wstawionego stałego ciągu, który powinien być dopasowany tylko wtedy, gdy znajduje się na początku wiersza. Właśnie to miałem na myśli za 1 $.
PSkocik,
3
Możesz to zrobić greprównież, ale najpierw musisz uciec od znaku specjalnego w swoim ciągu, np.printf %s ^;printf %s "$1" | sed 's/[][\.*^$]/\\&/g'; } | grep -f- infile
don_crissti
@don_crissti to lepsze niż niektóre inne odpowiedzi. Chcesz to zrobić?
roaima
@roaima - Wiem, ale jest tu już wiele odpowiedzi, a to (unikanie specjalnych znaków wewnątrz vars) jest czymś, co ja (i kilku innych użytkowników tutaj) wbijałem do domu od dłuższego czasu ... Zawsze możesz dodać to do twojej odpowiedzi, jeśli chcesz, a ja usunę komentarz tutaj (nie zapomnij dodać brakującego nawiasu klamrowego).
don_crissti

Odpowiedzi:

7

Nie mogę wymyślić sposobu, aby to zrobić za pomocą grep; ^samo w sobie jest częścią wyrażenia regularnego, więc użycie go wymaga interpretacji wyrażeń regularnych. To banalne korzystania dopasowywany podciąg w awk, perllub cokolwiek:

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

Aby obsłużyć ciągi zawierające \, możesz użyć tej samej sztuczki, co w odpowiedzi 123 :

search="$1" awk 'substr($0, 1, length(ENVIRON["search"])) == ENVIRON["search"] { print }'
Stephen Kitt
źródło
To nie zadziała dla ciągów takich jak\/
123
@ 123 rzeczywiście dodałem wariant, aby sobie z tym poradzić.
Stephen Kitt
Nadal nie powiedzie się w przypadku skomplikowanych ciągów, takich jak ten, \\\/\/\/\\\\/który jest widoczny \\///\\/w programie. O ile mi wiadomo, nie ma sposobu, aby właściwie uniknąć ukośników odwrotnych w awk, chyba że wiesz, ile z nich zostanie wykorzystanych wcześniej.
123
1
@ 123 dzięki, dostosowałem twoją sztuczkę przejścia przez środowisko, aby uniknąć przetwarzania ucieczki.
Stephen Kitt
Nadal podoba mi się to rozwiązanie. Wydajny (awk + brak marnowania czasu na rozglądanie się), szybkie uruchamianie (awk + brak dodatkowych procesów potrzebnych do konfiguracji stanu) korzysta ze standardowych narzędzi i jest dość zwięzły. We wszystkich pozostałych odpowiedziach brakuje przynajmniej niektórych z nich. (Wydajność jest tutaj mocną
stroną,
14

Jeśli musisz tylko sprawdzić, czy znaleziono dopasowanie, wytnij wszystkie linie wejściowe do długości żądanego prefiksu ( $1), a następnie użyj grep o stałym wzorze:

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

Łatwo jest również uzyskać liczbę pasujących linii:

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

Lub numery linii wszystkich pasujących linii (numery linii zaczynają się od 1):

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

Możesz podać numery wierszy headi tailuzyskać pełny tekst pasujących wierszy, ale w tym momencie łatwiej jest sięgnąć po nowoczesny język skryptowy, taki jak Python lub Ruby.

(W powyższych przykładach założono, że posix grep i cut. Zakładają, że plik do przeszukiwania pochodzi ze standardowego wejścia, ale można go łatwo dostosować do pobrania nazwy pliku.)

Edycja: Należy również upewnić się, że wzorzec ( $1) nie jest łańcuchem zerowym. W przeciwnym razie cutnie powie values may not include zero. Ponadto, jeśli używasz Bash, użyj, set -o pipefailaby złapać wyjścia błędów przez cut.

Lassi
źródło
10

Sposób użycia perla, który będzie szanował odwrotne ukośniki

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

To ustawia zmienną środowiskową v dla polecenia, a następnie drukuje, jeśli indeks zmiennej wynosi 0, tj. Początek linii.

Możesz także zrobić identycznie w awk

v="$1" awk 'index($0, ENVIRON["v"])==1' file
123
źródło
7

Oto opcja bash, nie polecam bash do przetwarzania tekstu, ale działa.

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

Skrypt oblicza długość lenwprowadzonego parametru $ 1, a następnie wykorzystuje rozwinięcie parametru w każdym wierszu, aby sprawdzić, czy pierwsze lenznaki pasują do 1 $. Jeśli tak, drukuje linię.

Jeff Schaller
źródło
4

Jeśli $1jest czysty ASCII i twój grepma -Popcję (aby umożliwić PCRE), można to zrobić:

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

Chodzi o to, że grep -Ppozwala wyrażeniom regularnym \xXXna określenie literałów, gdzie XXjest szesnastkowa wartość ASCII tego znaku. Znak jest dopasowywany dosłownie, nawet jeśli w przeciwnym razie jest specjalnym wyrażeniem regularnym.

odsłuży do konwertowania oczekiwanego początku linii na listę wartości szesnastkowych, które są następnie łączone razem, z których każda poprzedzona jest \xprzez printf. ^jest następnie poprzedzany tym ciągiem, aby utworzyć wymaganą regex.


Jeśli twój $1jest Unicode, staje się to nieco trudniejsze, ponieważ nie ma zgodności znaków 1: 1 z bajtami heksadecymalnymi od.

Cyfrowa trauma
źródło
3

Jako filtr:

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

Uruchom jeden lub więcej plików:

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

Sekcja „Cytowanie metaznaków” w dokumentacji perlre wyjaśnia:

Cytując metaznaki

Odwrotnym ukośnikiem metaznakami w Perl są alfanumeryczne, takie jak \b, \w, \n. W przeciwieństwie do niektórych innych języków wyrażeń regularnych, nie ma symboli odwróconych, które nie są alfanumeryczne. Więc coś, co wygląda \\, \(, \), \[, \], \{, lub \}jest zawsze interpretowana jako znak literalny, nie metaznak. Było to kiedyś używane we wspólnym idiomie do wyłączania lub cytowania specjalnego znaczenia metaznaków wyrażeń regularnych w ciągu, którego chcesz użyć dla wzorca. Po prostu zacytuj wszystkie znaki inne niż „słowo”:

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

(Jeśli use localejest ustawiony, zależy to od bieżących ustawień narodowych .) Obecnie częściej używa się quotemetafunkcji lub \Qsekwencji specjalnej metaquoting, aby wyłączyć specjalne znaczenie wszystkich metaznaków:

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

Uwaga: jeśli wstawisz dosłowne odwrotne ukośniki (te, które nie znajdują się w zmiennych interpolowanych) pomiędzy \Qi \E, interpolacja podwójnego cudzysłowu może prowadzić do mylących wyników. Jeśli potrzebujesz użyć dosłownych odwrotnych ukośników \Q...\E, zapoznaj się z „Krwawymi szczegółami parsowania cytowanych konstrukcji” w Perlop .

quotemetai \Qsą w pełni opisane w cytacie .

Greg Bacon
źródło
3

Jeśli twój grep ma opcję -P, co oznacza PCRE , możesz to zrobić:

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

Zapoznaj się z tym pytaniem i zapoznaj się z dokumentem PCRE, jeśli chcesz.

Bruce
źródło
2

Jeśli istnieje znak, którego nie używasz, możesz go użyć do zaznaczenia początku linii. Na przykład $'\a'(ASCII 007). Jest brzydki, ale zadziała:

{ 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

Jeśli nie potrzebujesz dopasowanych linii, możesz porzucić końcowy sedi użyć grep -qF. Ale jest o wiele łatwiejsze dzięki awk(lub perl) ...

roaima
źródło
0

Jeśli chcesz zajrzeć do pliku bez pętli, możesz użyć:
Wytnij plik o długości szukanego ciągu

  cut -c1-${#1} < file

Poszukaj stałych ciągów i numerów linii zwrotnych

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

Użyj numerów linii do czegoś takiego sed -n '3p;11p' file

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

Jeśli chcesz usunąć te linie, użyj

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