grepping uma string fixa no início de uma linha

18

grep "^$1" mais ou menos funciona, mas como eu escapei de "$1" , então o grep não interpreta nenhum caracter especialmente nele?

Ou existe uma maneira melhor?

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

    
por PSkocik 11.05.2016 / 10:46

8 respostas

7

Não consigo pensar em uma maneira de fazer isso usando grep ; ^ em si faz parte de uma expressão regular, portanto, usá-la exige que expressões regulares sejam interpretadas. É trivial usar correspondência de substring em awk , perl ou qualquer outra coisa:

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

Para lidar com sequências 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 }'
    
por 11.05.2016 / 10:48
13

Se você precisa apenas verificar se uma correspondência é encontrada, corte todas as linhas de entrada no comprimento do prefixo desejado ( $1 ) e, em seguida, 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 linha 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ê poderia alimentar os números de linha para head e tail para obter o texto completo das linhas correspondentes, mas, nesse ponto, é mais fácil procurar apenas uma linguagem de script moderna como Python ou Ruby.

(Os exemplos acima assumem 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.)

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

    
por 11.05.2016 / 17:32
10

Uma maneira de usar o perl, que respeitará as barras invertidas

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

Isso define a variável de ambiente v como o comando e, em seguida, 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
    
por 11.05.2016 / 11:26
6

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 len do parâmetro introduzido $ 1 e, em seguida, usa a expansão de parâmetro em cada linha para ver se os primeiros len caracteres correspondem a $ 1. Em caso afirmativo, imprime a linha.

    
por 11.05.2016 / 13:16
3

Como filtro:

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

Executar em um ou mais arquivos:

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

A seção "Quocar metacaracteres" da documentação em questão explica:

Quoting metacharacters

Backslashed metacharacters in Perl are alphanumeric, such as \b, \w, \n. Unlike some other regular expression languages, there are no backslashed symbols that aren’t alphanumeric. So anything that looks like \, \(, \), \[, \], \{, or \} is always interpreted as a literal character, not a metacharacter. This was once used in a common idiom to disable or quote the special meanings of regular expression metacharacters in a string that you want to use for a pattern. Simply quote all non-“word” characters:

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

(If use locale is set, then this depends on the current locale.) Today it is more common to use the quotemeta function or the \Q metaquoting escape sequence to disable all metacharacters’ special meanings like this:

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

Beware that if you put literal backslashes (those not inside interpolated variables) between \Q and \E, double-quotish backslash interpolation may lead to confusing results. If you need to use literal backslashes within \Q...\E, consult “Gory details of parsing quoted constructs” in perlop.

quotemeta and \Q are fully described in quotemeta.

    
por 11.05.2016 / 20:29
3

Se o seu $1 for puro ASCII e o seu grep tiver a opção -P (para 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 ideia aqui é que grep -P permite expressões regulares com \xXX para especificar caracteres literais, em que XX é o valor ASCII hexadecimal desse caractere. O caractere é correspondido literalmente, mesmo que seja um caractere regex especial.

od é usado para converter o início de linha esperado em uma lista de valores hexadecimais, que são então amarrados juntos, cada um prefixado com \x por printf. Em seguida, ^ é prefixado nessa string para criar a regex necessária.

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

    
por 11.05.2016 / 23:36
2

Se houver um caractere que você não usa, você poderá 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) combinada (s), poderá descartar o sed à direita e usar grep -qF . Mas é muito mais fácil com awk (ou perl ) ...

    
por 11.05.2016 / 11:07
0

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

  cut -c1-${#1} < file

Procure por cadeias fixas e números de linha de retorno

  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ê quiser excluir essas linhas, use

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

Tags