recursive estructura espaƱol bash shell grep

bash - estructura - Capturando grupos de un regEx Grep



ls grep (7)

Tengo este pequeño script en sh (Mac OSX 10.6) para examinar una serie 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, tu nombre para los gurús) $name solo tiene 0, 1 o 2, dependiendo de si grep encontró que el nombre del archivo coincide con la materia proporcionada. Lo que me gustaría es capturar lo que está dentro de los parens ([az]+) y almacenarlo en una variable .

Me gustaría usar solo grep , si es posible . Si no, no Python o Perl, etc. sed o algo así. Soy nuevo en shell y me gustaría atacar esto desde el ángulo purista de * nix.

Además, como bonitas super-geniales , tengo curiosidad por saber cómo puedo concatenar cadenas en shell. ¿El grupo que capturé era la cadena "somename" almacenada en $ name, y quería agregar la cadena ".jpg" al final de la misma, podría cat $name ''.jpg'' ?

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


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

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

usar solo hacer

$ echo ''hello world'' | regex1 ''hello/s(.*)'' world


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

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

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

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

El primer grep eliminaría cualquier línea que no coincidiera con su padre general, el segundo grep (que tiene --only-matching especificada) mostrará la parte alfa del nombre. Esto solo funciona porque el patrón es adecuado: la "porción alfa" es lo suficientemente específica 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 se cut para analizar la línea en campos al dividir 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 hacerlas bien, y combinarlas para lograr tareas no triviales, así que diría que grep + sed etc. es una forma más Unixy de hacer las cosas :-)


Me doy cuenta de que ya se aceptó una respuesta para esto, pero desde un "ángulo purista estrictamente * nix" parece que la herramienta correcta para el trabajo es pcregrep , que no parece haber sido mencionada 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 los contenidos del grupo de captura 1.

La herramienta pcregrep utiliza la misma sintaxis que ya usó con grep , pero implementa la funcionalidad que necesita.

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 cambio en el script. Simplemente reemplace una utilidad modular con otra y modifique los parámetros.

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


No es posible en solo grep creo

para sed:

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

Sin embargo, voy a probar el bono:

echo "$name.jpg"


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 existe) en el índice 2, etc. El índice cero es la coincidencia completa.

Debe tener en cuenta que sin anclas, esta expresión regular (y la que usa grep ) coincidirá con cualquiera de los siguientes ejemplos y más, que pueden no ser lo que está buscando:

123_abc_d4e5 xyz123_abc_d4e5 123_abc_d4e5.xyz xyz123_abc_d4e5.xyz

Para eliminar los ejemplos segundo y cuarto, haga su expresión regular como esta:

^[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 agrega 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 se eliminará ya que el punto no está entre los caracteres en la expresión regular y el signo de dólar representa el final de la cadena. Tenga en cuenta que el cuarto ejemplo también falla esta coincidencia.

Si tiene GNU grep (alrededor de 2.5 o posterior, creo que cuando se agregó el operador /K ):

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

El operador /K (look-behind de longitud variable) 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. Debe usar /K si los cuantificadores pueden coincidir con cadenas de diferentes longitudes (por ejemplo, + , * , {2,4} ).

El operador (?=) Hace coincidir 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 no distinga entre mayúsculas y minúsculas, se utiliza el operador (?i) . Afecta los patrones que lo siguen por lo que su posición es significativa.

Es posible que sea necesario ajustar la expresión regular dependiendo de si hay otros caracteres en el nombre de archivo. Notarás que en este caso, muestro un ejemplo de concatenación de una cadena al mismo tiempo que se captura la subcadena.


Si tienes bash, puedes usar globos extendidos.

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


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

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

Entonces el name tendrá el valor abc .

Consulte la documentación del desarrollador de Apple, busque "Expansión de parámetros".