grepping una cadena fija al comienzo de una línea

20

grep "^$1"funciona, pero ¿cómo escapo "$1"para que grep no interprete ningún personaje en él especialmente?

¿O hay un mejor camino?

Editar: no quiero buscar '^$1'sino una cadena fija insertada dinámicamente que solo debe coincidir si está al comienzo de una línea. Eso es lo que quise decir con el $1.

PSkocik
fuente
¿Intentó utilizar comillas simples en lugar de comillas dobles, por ejemplo grep '^$1'? ¿O no quiso decir que desea evitar $1que el shell lo expanda?
mnille
@mnille No quiero buscar '^ $ 1' sino una cadena fija insertada dinámicamente que solo debería coincidir si está al comienzo de una línea. Eso es lo que quise decir con $ 1.
PSkocik
3
También puede hacerlo, greppero primero tendrá que escapar de cualquier carácter especial en su cadena, por ejemploprintf %s ^;printf %s "$1" | sed 's/[][\.*^$]/\\&/g'; } | grep -f- infile
don_crissti
@don_crissti eso es mejor que algunas de las otras respuestas. ¿Te importaría hacerlo uno?
roaima
@roaima: lo sé, pero ya hay un montón de respuestas aquí y esto (escapar de los caracteres especiales dentro de vars) es algo que yo (y un par de otros usuarios aquí) he estado martillando en casa durante bastante tiempo ... Siempre puedes agregar si lo desea, elimine el comentario aquí (no olvide agregar el paréntesis que falta).
don_crissti

Respuestas:

7

No puedo pensar en una forma de hacer esto usando grep; ^en sí mismo es parte de una expresión regular, por lo que su uso requiere la interpretación de expresiones regulares. Es trivial usar la coincidencia de subcadenas awk, perlo lo que sea:

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

Para manejar las cadenas de búsqueda que contienen \, puede usar el mismo truco que en la respuesta de 123 :

search="$1" awk 'substr($0, 1, length(ENVIRON["search"])) == ENVIRON["search"] { print }'
Stephen Kitt
fuente
Esto no funcionará para cadenas como\/
123
@ 123 de hecho, he agregado una variante para manejar eso.
Stephen Kitt
Todavía fallará para cadenas complicadas como las \\\/\/\/\\\\/que se ven \\///\\/en el programa. Hasta donde sé, no hay forma de escapar de las barras invertidas en awk, a menos que sepa cuántos se usarán de antemano.
123
1
@ 123 gracias, he adaptado tu truco de recorrer el entorno para evitar el procesamiento de escape.
Stephen Kitt
Todavía me gusta esta solución lo mejor. Eficiente (awk + sin perder tiempo mirando a su alrededor), el inicio rápido (awk + no se necesitan procesos adicionales para configurar el estado) utiliza herramientas estándar y es bastante conciso. Todas las otras respuestas carecen al menos algunas de estas. (La eficiencia es un punto fuerte aquí ya que grep es conocido por su velocidad inigualable).
PSkocik
14

Si solo necesita verificar si se encuentra o no una coincidencia, corte todas las líneas de entrada a la longitud del prefijo deseado ( $1) y luego use grep de patrón fijo:

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

También es fácil obtener el recuento de líneas coincidentes:

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

O los números de línea de todas las líneas coincidentes (los números de línea comienzan en 1):

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

Puede alimentar los números de línea heady tailobtener el texto completo de las líneas coincidentes, pero en ese punto es más fácil acceder a un lenguaje de script moderno como Python o Ruby.

(Los ejemplos anteriores suponen que Posix grep and cut. Asumen que el archivo a buscar proviene de una entrada estándar, pero puede adaptarse fácilmente para tomar un nombre de archivo).

Editar: también debe asegurarse de que el patrón ( $1) no sea una cadena de longitud cero. De lo contrario cutno se puede decir values may not include zero. Además, si usa Bash, use set -o pipefailpara atrapar salidas de error por cut.

Lassi
fuente
10

Una forma de usar perl que respetará las barras invertidas

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

Esto establece la variable de entorno v para el comando, luego imprime si el índice de la variable es 0, es decir, el comienzo de la línea.

También puedes hacer lo mismo en awk

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

Aquí hay una opción de todo bash, no es que recomiende bash para el procesamiento de texto, pero 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

El script calcula la longitud lendel parámetro ingresado $ 1, luego usa la expansión de parámetros en cada línea para ver si los primeros lencaracteres coinciden con $ 1. Si es así, imprime la línea.

Jeff Schaller
fuente
4

Si su $1es ASCII puro y greptiene la -Popción (para habilitar PCRE), puede hacer esto:

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

La idea aquí es que grep -Ppermite que las expresiones regulares \xXXespecifiquen caracteres literales, donde XXestá el valor ASCII hexadecimal de ese carácter. El carácter coincide literalmente, incluso si es un carácter regex especial.

odse usa para convertir el inicio de línea esperado en una lista de valores hexadecimales, que luego se unen, cada uno con el prefijo \xprintf. ^luego se antepone esta cadena para construir la expresión regular requerida.


Si su $1es unicode, entonces esto se vuelve un poco más difícil, porque no hay una correspondencia 1: 1 de caracteres a bytes hexadecimales como salida por od.

Trauma digital
fuente
3

Como un filtro:

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

Ejecutar en uno o más archivos:

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

La sección "Citar metacaracteres" de la documentación de perlre explica:

Citando metacaracteres

Metacaracteres con barras invertidas en Perl son alfanuméricos, tales como \b, \w, \n. A diferencia de otros lenguajes de expresiones regulares, no hay símbolos con barra invertida que no sean alfanuméricos. Así que cualquier cosa que se ve como \\, \(, \), \[, \], \{, o \}siempre se interpreta como un carácter literal, no es una meta-carácter. Esto se usó una vez en un idioma común para deshabilitar o citar los significados especiales de los metacaracteres de expresiones regulares en una cadena que desea usar para un patrón. Simplemente cite todos los caracteres que no sean "palabras":

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

(Si use localeestá configurado, esto depende de la configuración regional actual). Hoy en día es más común usar la quotemetafunción o la \Q secuencia de escape de metaquotes para deshabilitar los significados especiales de todos los metacaracteres como este:

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

Tenga en cuenta que si coloca barras diagonales invertidas (aquellas que no están dentro de las variables interpoladas) entre \Qy \E, la interpolación de barra invertida con comillas dobles puede generar resultados confusos. Si necesita utilizar barras diagonales inversas \Q...\E, consulte "Detalles sangrientos de analizar construcciones citadas" en perlop .

quotemetay \Qestán completamente descritos en quotemeta .

Greg Bacon
fuente
3

Si su grep tiene la opción -P, que significa PCRE , puede hacer esto:

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

Consulte esta pregunta y consulte el documento PCRE para obtener detalles si lo desea.

Bruce
fuente
2

Si hay un carácter que no usa, puede usarlo para marcar el comienzo de la línea. Por ejemplo, $'\a'(ASCII 007). Es feo pero 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

Si no necesita la (s) línea (s) coincidentes (s), puede soltar el final sedy usar grep -qF. Pero es mucho más fácil con awk(o perl) ...

roaima
fuente
0

Cuando desee buscar en un archivo sin bucle, puede usar:
Corte el archivo con la longitud de la cadena de búsqueda

  cut -c1-${#1} < file

Busque cadenas fijas y números de línea de retorno

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

Use los números de línea para algo como sed -n '3p;11p' file

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

Cuando desee eliminar estas líneas, use

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