Captura de Grupos Desde una Expresión Regular Grep


Tengo este pequeño script en sh (Mac OSX 10.6) para mirar a través de una matriz de archivos. Google ha dejado de ser útil en este punto:

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

Hasta ahora (obviamente, para los gurús de shell) $name simplemente contiene 0, 1 o 2, dependiendo de si grep encontró que el nombre del archivo coincidía con el asunto proporcionado. Lo que me gustaría es capturar lo que está dentro de los paréntesis ([a-z]+) y almacenarlo en una variable.

Me gustaría usar grep solo, si es posible. Si no, por favor no Python o Perl, etc. sed o algo parecido: soy nuevo en shell y me gustaría atacar esto desde el ángulo de *nix purist.

También, como un super-cool bonu s, tengo curiosidad en cuanto a cómo puedo concatenar cadena en shell? Es el grupo que capturé fue la cadena "somename" almacenada en name name, y quería agregar la cadena ".jpg " hasta el final, podría cat $name '.jpg'?

Por favor, explique lo que está pasando, si tiene tiempo.

Author: royhowie, 2009-12-12

7 answers

Si estás usando Bash, ni siquiera tienes que usar grep:

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

Es mejor poner la expresión regular en una variable. Algunos patrones no funcionarán si se incluyen literalmente.

Esto usa =~ que es el operador de coincidencia de expresiones regulares de Bash. Los resultados de la coincidencia se guardan en una matriz llamada $BASH_REMATCH. El primer grupo de captura se almacena en el índice 1, el segundo (si lo hay) en el índice 2, etc. El índice cero es la coincidencia completa.

Usted debe tener en cuenta que sin anclas, esta expresión regular (y el usando grep) coincidirá con cualquiera de los siguientes ejemplos y más, que puede no ser lo que está buscando:

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

Para eliminar el segundo y cuarto ejemplos, haga su expresión regular de esta manera:

^[0-9]+_([a-z]+)_[0-9a-z]*

Que dice que la cadena debe comenzar con uno o más dígitos. El quilate representa el comienzo de la cadena. Si agregas un signo de dólar al final de la expresión regular, así:

^[0-9]+_([a-z]+)_[0-9a-z]*$

Entonces el tercer ejemplo también será eliminado ya que el punto no está entre los los caracteres en la expresión regular y el signo de dólar representan el final de la cadena. Tenga en cuenta que el cuarto ejemplo falla esta coincidencia también.

Si tienes GNU grep (alrededor de la 2.5 o posterior, creo, cuando se agregó el operador \K):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

El operador \K (variable-length look-behind) hace que el patrón anterior coincida, pero no incluye la coincidencia en el resultado. El equivalente de longitud fija es (?<=) - el patrón se incluiría antes del paréntesis de cierre. Usted debe usar \K si los cuantificadores pueden coincidir con cadenas de diferentes longitudes (p. ej. +, *, {2,4}).

El operador (?=) coincide con patrones de longitud fija o variable y se llama "mirar hacia adelante". Tampoco incluye la cadena coincidente en el resultado.

Para hacer que la coincidencia sea insensible a mayúsculas y minúsculas, se utiliza el operador (?i). Afecta a los patrones que lo siguen por lo que su posición es significativa.

La expresión regular podría necesitar ser ajustada dependiendo de si hay son otros caracteres en el nombre del archivo. Notará que en este caso, muestro un ejemplo de concatenar una cadena al mismo tiempo que se captura la subcadena.

 382
Author: Dennis Williamson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-07 18:45:21

Esto no es realmente posible con pure grep, al menos no en general.

Pero si su patrón es adecuado, es posible que pueda usar grep varias veces dentro de una canalización para reducir primero su línea a un formato conocido, y luego extraer solo el bit que desee. (Aunque herramientas como cut y sed son mucho mejores en esto).

Supongamos por el bien del argumento que su patrón era un poco más simple: [0-9]+_([a-z]+)_ Podría extraer esto de esta manera:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

El primero grep sería elimine cualquier línea que no coincida con su patrón general, el segundo grep (que tiene --only-matching especificado) mostrará la porción alfa del nombre. Esto solo funciona porque el patrón es adecuado:" alpha portion " es lo suficientemente específico como para extraer lo que desea.

(Aparte: Personalmente usaría grep + cut para lograr lo que buscas: echo $name | grep {pattern} | cut -d _ -f 2. Esto hace que cut analice la línea en campos dividiendo en el delimitador _, y devuelve solo el campo 2 (los números de campo comienzan en 1)).

La filosofía de Unix es tener herramientas que hagan una cosa, y lo hagan bien, y las combinen para lograr tareas no triviales, así que yo diría que grep + sed etc es una forma más Unixy de hacer las cosas: -)

 122
Author: RobM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-12 01:26:04

Me doy cuenta de que ya se aceptó una respuesta para esto, pero desde un" ángulo purista estrictamente *nix " parece que la herramienta adecuada para el trabajo es pcregrep, que no parece haber sido mencionado todavía. Intenta cambiar las líneas:

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

A lo siguiente:

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

Para obtener solo el contenido del grupo de captura 1.

El pcregrep la herramienta utiliza la misma sintaxis con la que ya has usado grep, pero implementa la funcionalidad que lo necesitas.

El parámetro -o funciona igual que la versión grep si está vacía, pero también acepta un parámetro numérico en pcregrep, que indica qué grupo de captura desea mostrar.

Con esta solución se requiere un mínimo de cambios en el script. Simplemente reemplace una utilidad modular con otra y ajuste los parámetros.

Nota interesante: Puede usar varios argumentos - o para devolver varios grupos de captura en el orden en la que aparecen en la línea.

 74
Author: John Sherwood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-03 17:14:08

No es posible en solo grep creo

Para sed:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`

Voy a tomar una puñalada en el bono sin embargo:

echo "$name.jpg"
 22
Author: cobbal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-12 01:17:33

Esta es una solución que usa gawk. Es algo que encuentro que necesito usar a menudo, así que creé una función para él

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

Para usar solo hacer

$ echo 'hello world' | regex1 'hello\s(.*)'
world
 11
Author: opsb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-09 06:37:31

Una sugerencia para usted-puede usar la expansión de parámetros para eliminar la parte del nombre desde el último guion bajo en adelante, y de manera similar al inicio:

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

Entonces name, tendrá el valor abc.

Vea Apple developer docs, busque 'Parameter Expansion'.

 2
Author: martin clayton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-12 01:16:46

Si tienes bash, puedes usar globbing extendido

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

O

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done
 1
Author: ghostdog74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-12 04:12:25