Grep para el patrón al inicio o en el medio de una línea

9

Comenzaré diciendo que creo que este problema es un poco menos inocente de lo que parece.

Lo que necesito hacer: buscar una carpeta dentro de la variable de entorno PATH. Podría ser al principio o en algún lugar después. Solo necesito verificar que esa carpeta esté allí.

Ejemplo de mi problema: usemos /opt/gnome.


ESCENARIO 1: la carpeta no está al comienzo de PATH

# echo "$PATH"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

# echo "$PATH" | grep ":/opt/gnome"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

Tenga en cuenta que el grep debe ser lo suficientemente específico para que no se atrape /var/opt/gnome. De ahí el colon.


ESCENARIO 2: la carpeta está al comienzo de PATH.

# echo "$PATH"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

# echo "$PATH" | grep "^/opt/gnome"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

Este es mi problema: necesito buscar dos puntos o un inicio de línea con esta carpeta. Lo que me gustaría hacer es una de estas dos expresiones de paréntesis:

# echo $PATH | grep "[^:]/opt/gnome"
# echo $PATH | grep "[:^]/opt/gnome"

PERO [^y [:tienen sus propios significados. Por lo tanto, los dos comandos anteriores no funcionan.

¿Hay alguna forma en que pueda manejar estos dos escenarios en un solo comando?

JamesL
fuente
Tenga en cuenta que el comentario de Gilles sobre la respuesta de Costas también se aplica a la pregunta: dado que no está buscando /opt/gnome:o /opt/gnome$, encontrará /opt/gnome-fooo /opt/gnome/bar.
Scott
@Scott: siempre que incluyas en tu partida el espacio intermedio, siempre puedes anclar cualquier cuerda en la cabeza y la cola de la línea sin tales complicaciones. Justo comogrep '^\(any number of other matches:*:\)*my match\(:.*\)*$'
mikeserv

Respuestas:

10

Si está comprobando el contenido de la PATHvariable de entorno, en lugar de buscar algo en un archivo, entonces grepes la herramienta incorrecta. Es más fácil (y más rápido y posiblemente más legible) hacerlo en el shell.

En bash, ksh y zsh:

if [[ :$PATH: = *:/opt/gnome:* ]]; then
 : # already there
else
  PATH=$PATH:/opt/gnome
fi

Portablemente:

case :$PATH: in
  *:/opt/gnome:*) :;; # already there
  *) PATH=$PATH:/opt/gnome;;
esac

Tenga en cuenta el uso de en :$PATH:lugar de $PATH; de esta manera, el componente siempre está rodeado de dos puntos en la cadena de búsqueda, incluso si estaba al principio o al final de $PATH.

Si está buscando a través de una línea de un archivo, puede usar la expresión regular extendida (es decir, requerida grep -E) (^|:)/opt/gnome($|:)para que coincida, /opt/gnomepero solo si está al principio de una línea o después de dos puntos, y solo si está al final de la línea o seguido de dos puntos.

Gilles 'SO- deja de ser malvado'
fuente
8

Puede usar expresiones regulares extendidas simplemente usando grep -E

Tiene que coincidir con el principio y el final del camino que está intentando encontrar si desea evitar falsos positivos.

Coincide con la instancia al principio:

$ TEST=/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

También coincide con la instancia en el medio:

$ TEST=/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

Evitar falsos positivos:

$ TEST="/home/bob/opt/gnome:/opt/gnome/somethingelse:/opt/gnome-beta"
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"

No hay coincidencias allí.

Compacto y elegante. Probado en Debian 7.

Luis Antolín Cano
fuente
1
egrepes el uso en desuso grep -E(fuente: man grep)
Anthon
Gracias, funciona como un encanto! Sin embargo, no lo elegí como respuesta, porque creo que la opción -w es un poco más simple. ¡Incluso más simple de lo que había imaginado originalmente!
JamesL
3
Advertencia. La -wopción tiene algunos problemas. Solo los dígitos, las letras y el guión bajo se consideran "palabras". Por lo tanto, algunos caracteres inusuales pero posibles harán que falle. Ejemplo echo '/sbin:/usr/sbin:/var-/opt/gnome' | grep -w "/opt/gnome"y echo '/sbin:/usr/sbin:/var./opt/gnome' | grep -w "/opt/gnome". Esos entregan resultados incorrectos.
Luis Antolín Cano
1
Usted está en el camino correcto, pero todavía hay falsos positivos: /opt/gnome/somethingelse.
Gilles 'SO- deja de ser malvado'
1
Totalmente cierto. Deberíamos preocuparnos explícitamente por el final, no solo por el principio. Creo que esto soluciona los problemas echo "/home/bob/opt/gnome:/opt/gnome/somethingelse:/opt/gnome-beta" | grep -E "(:|^)/opt/gnome(:|$)". Editando respuesta.
Luis Antolín Cano
7

Si no está casado grep, puede usar awky separar los registros en:

awk 'BEGIN {RS=":"} /^\/opt\/gnome$/'
jasonwryan
fuente
5

También podrías usar

echo "$PATH" | tr ':' '\n' | grep -x "/opt/gnome"

que divide la variable de ruta en líneas separadas (una por ruta), por lo que grep -xpuede buscar resultados exactos. Por supuesto, esto tiene la desventaja de que necesita un proceso adicional tr. Y no funcionará cuando el nombre de una carpeta PATHcontenga caracteres de nueva línea.

TBrandt
fuente
2

No sé si es suficiente para responder pero

grep -w "/opt/gnome"

satisfará su necesidad

echo '/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome' | grep -w "/opt/gnome" -o
/opt/gnome
echo '/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome' | grep -w "/opt/gnome" -o
/opt/gnome

pero

echo '/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome' | grep "/opt/gnome" -o
/opt/gnome
/opt/gnome
Costas
fuente
Esto funciona perfectamente porque los dos puntos son caracteres que no son palabras. ¡Gracias!
JamesL
@ Sman865 Hay otra razón: porque /no es parte de la palabra, pero lo res.
Costas
2
Advertencia. Como dije en el comentario sobre mi respuesta. Hay caracteres legales para un nombre de directorio que no son caracteres de palabras. Eso lleva a resultados incorrectos. No es habitual finalizar un nombre de directorio, pero podría suceder.
Luis Antolín Cano
44
@ Sman865 Falsos positivos: /opt/gnome-beta, /home/bob/opt/gnome, ...
Gilles 'SO-stop ser maligno'
No funciona: grep -w /usr/local -o <<< /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games------/usr/local /usr/local /usr/local
pabouk
0

Para seleccionar /opt/gnomerodeado de caracteres que no son de texto (nuevas líneas, :, /, etc.) tratan éste:

grep '\B/opt/gnome'
jimmij
fuente
0

Puede hacer esto de manera confiable y con poco esfuerzo grep. Puede aprovechar las extensiones que están ampliamente disponibles y entre las cuales ya se han ofrecido muchas soluciones, pero incluso con expresiones regulares básicas se hace fácilmente, aunque puede que no sea intuitivamente a primera vista.

Con regex básico, y así sucesivamente grep, siempre tiene dos anclajes confiables: la cabeza y la cola de la línea. Puede anclar una coincidencia a ambos independientemente de su ubicación en la línea como:

grep '^\(ignore case, delimiter\)*match\(delimiter, ignore case\)*$'

grepcoincidirá desde la cabecera de la línea con tantas ocurrencias de las \(grouped\)subexpresiones como sea necesario para encontrar el siguiente delimitador y luego la coincidencia explícita, y desde la cola de su coincidencia hasta la cola de la línea de la misma manera. Si su coincidencia explícita no coincide explícitamente , fallará y no imprimirá nada.

Y así podría hacer, por ejemplo:

grep '^\(.*:\)*/opt/gnome\(:.*\)*$'

Ver por ti mismo:

grep '^\(.*:\)*/opt/gnome\(:.*\)*$
' <<\INPUT
/opt/gnome-beta
/opt/gnome
/home/bob/opt/gnome
:/opt/gnome:
/home/bob/opt/gnome:/opt/gnome:/opt/gnome-beta
/opt-gnome-beta
/opt/gnomenot::::/opt/gnome
INPUT

SALIDA

/opt/gnome
:/opt/gnome:
/home/bob/opt/gnome:/opt/gnome:/opt/gnome-beta
/opt/gnomenot::::/opt/gnome
mikeserv
fuente
0

ha notado un caso de borde ... podría evitarlo forzando la aparición de a: al comienzo de la línea:

 echo ":$PATH" | grep ":/opt/gnome"

o si la ruta es exacta, agregue también una al final para asegurarse de que esté acotada:

 echo ":${PATH}:" | grep ":/opt/gnome:"
Olivier Dulac
fuente