grepping uma string fixa no início de uma linha


20

grep "^$1"funciona, mas como posso escapar "$1"para que o grep não interprete nenhum personagem nele especialmente?

Ou há uma maneira melhor?

Edit: Eu não quero procurar, '^$1'mas por uma string fixa inserida dinamicamente que só deve ser correspondida se estiver no início de uma linha. Foi isso que eu quis dizer com $1.


Você tentou usar aspas simples em vez de aspas duplas, por exemplo grep '^$1'? Ou você não quis dizer que deseja impedir que $1o shell seja expandido?
Mnille

@mnille Eu não quero procurar por '^ $ 1', mas por uma string fixa inserida dinamicamente, que só deve ser correspondida se estiver no início de uma linha. Isso é o que eu quis dizer com $ 1.
PSkocik

3
Você pode fazê-lo com grepmuito, mas você vai ter que escapar qualquer caractere especial em sua cadeia de primeira por exemploprintf %s ^;printf %s "$1" | sed 's/[][\.*^$]/\\&/g'; } | grep -f- infile
don_crissti

@don_crissti isso é melhor do que algumas das outras respostas. Gostaria de torná-lo um?
roaima 14/05

@roaima - Eu sei, mas já existem várias respostas aqui e isso (escapar dos caracteres especiais dentro dos vars) é algo que eu (e alguns outros usuários aqui) estamos martelando em casa há algum tempo ... Você sempre pode adicionar se desejar, removerei o comentário aqui (não se esqueça de adicionar a chave que falta).
31416 don_crissti

Respostas:


7

Não consigo pensar em uma maneira de fazer isso usando grep; ^ele próprio faz parte de uma expressão regular, portanto, para usá-lo, é necessário interpretar expressões regulares. É trivial usar a correspondência de substring no awk, perlou o que for:

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

Para lidar com as cadeias de pesquisa que contêm \, você pode usar o mesmo truque da resposta do 123 :

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

Isso não funcionará para cadeias de caracteres como:\/
123

@ 123 de fato, adicionei uma variante para lidar com isso.
Stephen Kitt

Ainda falhará em seqüências complicadas, como as \\\/\/\/\\\\/que são vistas \\///\\/no programa. Tanto quanto sei, não há como escapar adequadamente de barras invertidas no awk, a menos que você saiba quantas serão usadas anteriormente.
123

11
@ 123 obrigado, adaptei seu truque de passar pelo ambiente para evitar o processamento de escape.
Stephen Kitt

Eu ainda gosto desta solução da melhor maneira. Eficiente (awk + sem perda de tempo olhando ao redor), a inicialização rápida (awk + sem processos adicionais necessários para configurar o estado) usa ferramentas padrão e é bastante conciso. Todas as outras respostas carecem de pelo menos algumas delas. (Eficiência é um ponto forte aqui como grep é conhecida por velocidade incomparável.)
PSkocik

14

Se você apenas precisar verificar se uma correspondência foi ou não encontrada, corte todas as linhas de entrada no comprimento do prefixo desejado ( $1) e use grep de padrão fixo:

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

Também é fácil obter a contagem de linhas correspondentes:

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

Ou os números de todas as linhas correspondentes (os números de linha começam em 1):

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

Você pode alimentar os números das linhas heade tailobter o texto completo das linhas correspondentes, mas nesse momento é mais fácil acessar uma linguagem de script moderna como Python ou Ruby.

(Os exemplos acima assumem o Posix grep e cut. Eles assumem que o arquivo a ser pesquisado vem da entrada padrão, mas pode ser facilmente adaptado para usar um nome de arquivo.)

Editar: Você também deve garantir que o padrão ( $1) não seja uma string de comprimento zero. Caso contrário, cutfalha ao dizer values may not include zero. Além disso, se estiver usando o Bash, use set -o pipefailpara capturar as saídas de erro por cut.


10

Uma maneira de usar perl que respeite as barras invertidas

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

Isso define a variável de ambiente v para o comando e depois imprime se o índice da variável for 0, ou seja, o início da linha.

Você também pode fazer o mesmo no awk

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

7

Aqui está uma opção all-bash, não que eu recomende o bash para processamento de texto, mas funciona.

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

O script calcula o comprimento lendo parâmetro inserido $ 1 e usa a expansão do parâmetro em cada linha para ver se os primeiros lencaracteres correspondem a $ 1. Nesse caso, imprime a linha.


4

Se você $1é ASCII puro e greptem a -Popção (ativar o PCRE), você pode fazer isso:

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

A idéia aqui é que grep -Ppermita expressões regulares com \xXXpara especificar caracteres literais, onde XXestá o valor ASCII hexadecimal desse caractere. O caractere é correspondido literalmente, mesmo que seja um caractere de regex especial.

odé usado para converter o início de linha esperado em uma lista de valores hexadecimais, que são então agrupados, cada um prefixado \xpor printf. ^é então anexada essa sequência para criar o regex necessário.


Se você $1é unicode, isso se torna um pouco mais difícil, porque não há uma correspondência 1: 1 de caracteres para bytes hexadecimais como saída od.


3

Como filtro:

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

Execute em um ou mais arquivos:

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

A seção "Citando metacaracteres" da documentação do perlre explica:

Citando metacaracteres

Com barra invertida metacharacters em Perl são alfanuméricos, como \b, \w, \n. Ao contrário de outras linguagens de expressão regular, não há símbolos com barra invertida que não sejam alfanuméricos. Então, qualquer coisa que se parece com \\, \(, \), \[, \], \{, ou \}é sempre interpretado como um caractere literal, não é um metacaractere. Isso já foi usado em um idioma comum para desativar ou citar os significados especiais dos metacaracteres de expressão regular em uma sequência que você deseja usar para um padrão. Simplesmente cite todos os caracteres que não são “palavra”:

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

(Se use localeestiver definido, isso depende quotemetado \Q código do idioma atual.) Hoje é mais comum usar a função ou a sequência de escape de metaquoting para desativar todos os significados especiais de todos os metacaracteres como este:

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

Lembre-se de que, se você colocar barras invertidas literais (aquelas que não estão dentro de variáveis ​​interpoladas) entre \Qe \E, a interpolação de barra invertida com aspas duplas pode levar a resultados confusos. Se você precisar usar barras invertidas literais \Q...\E, consulte “Detalhes sangrentos da análise de construções citadas” no perlop .

quotemetae \Qsão totalmente descritos em quotemeta .


3

Se o seu grep tem a opção -P, que significa PCRE , você pode fazer o seguinte:

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

Consulte esta pergunta e consulte o documento PCRE para obter detalhes, se desejar.


2

Se houver um caractere que você não usa, você pode usá-lo para marcar o início da linha. Por exemplo, $'\a'(ASCII 007). É feio, mas vai funcionar:

{ 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

Se você não precisar da (s) linha (s) correspondente (s), poderá soltar a trilha sede usá-la grep -qF. Mas é muito mais fácil com awk(ou perl) ...


0

Quando você deseja procurar em um arquivo sem loop, pode usar:
Recorte o arquivo com o comprimento da string de pesquisa

  cut -c1-${#1} < file

Procure por seqüências fixas e retorne números de linha

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

Use os números de linha para algo como sed -n '3p;11p' file

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

Quando você deseja excluir essas linhas, use

  sed "$(grep -Fn "$1" <(cut -c1-${#1} < file) | sed 's/:.*/d;/' | tr -d '\n')" file
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.