Coincidencia no codiciosa en sed

Coincidencia no codiciosa en sed

En un script bash, tengo la siguiente variable:

file_name='this_is_the_hart_part.csv'

Usando

var2=$(echo $file_name | sed -e 's/_{2}\(.*\)_{3}/\1/')

Quiero extraer la subcadena "the" (entre los guiones bajos número 2 y 3 en la variable $file_name).

Pero obtengo $var2 igual a $file_name. ¿Cómo tengo que cambiar mi comando sed?

Respuesta1

Los tipos de expresiones regulares admitidas por sedno permiten coincidencias no codiciosas con *.

Quiere obtener el tercer _campo delimitado. Esto se hace más fácilmente con cut:

cut -d '_' -f 3

O con awk:

awk -F '_' '{ print $3 }'

O, en el shell, eliminando los dos primeros campos sucesivamente y luego recortando el final:

str=${file_name#*_}
str=${str#*_}
str=${str%%_*}

"$str"sería la palabra theal final. Usar esta última variación probablemente sería la forma más rápida y sólida de salir de estas tres.

La sustitución de variables ${variable#*_}daría como resultado una cadena en la que $variablese eliminaría el bit inicial hasta el primer guión bajo incluido. Eliminaría ${variable%%_*}todo, desde el primer guión bajo hasta el final $variable. Estas son sustituciones de variables estándar.

El beneficio de usar la sustitución de variables en un nombre de archivo es que podría manejar nombres de archivo que contienen nuevas líneas, lo cual ni ni awkni sedharían cut. En general, no utilice herramientas de edición de texto orientadas a líneas en los nombres de archivos.

Además, estás usando echo $file_name. Dado que $file_nameno está entrecomillado, se someterá a una división de palabras (en cada carácter que también forma parte de $IFS; un espacio, tabulación y nueva línea de forma predeterminada) y las palabras generadas, si contienen caracteres globales en el nombre del archivo, se compararán con los nombres de archivo en el directorio actual. por el caparazón. Y las barras invertidas en el nombre del archivo también pueden desaparecer o tener efectos no deseados (incluso si cita la expansión). El kshshell también haría expansiones de llaves en el valor de $file_namecuando no está entrecomillado.

Respuesta2

Primera nota que sedes untextoutilidad que funciona de forma predeterminada en una línea a la vez, mientras que los nombres de archivos pueden contener cualquier carácter (incluida la nueva línea) e incluso no caracteres (pueden no ser caracteres).texto).

También,dejar una variable sin comillas tiene un significado muy especial, casi nunca quieres hacer eso, también espotencialmente muy peligroso.

También,no puedes usarlo echopara generar datos arbitrarios, usa printfen su lugar.

Además, la sintaxis de asignación de variables en shells tipo Bourne es: var=value, no $var=value.

Puede cargar toda la salida de echo(o mejor printf) en sedel espacio del patrón con:

printf '%s\n' "$filename" | sed -e :1 -e '$!{N;b1' -e '}'

Luego, puedes agregar el código para extraer la parte entre el segundo y el tercero _:

var2=$(
  printf '%s\n' "$filename" |
   sed -ne :1 -e '$!{N;b1' -e '}' -e 's/^\([^_]*_\)\{2\}\([^_]*\)_.*/\2/p'
)

La parte no codiciosa se aborda mediante el uso [^_]*(una secuencia de no _caracteres) que, contrariamente a .*las garantías, no coincide con _los límites pasados ​​(aunque todavía se ahogaría con los no caracteres en muchas implementaciones).

En este caso, podría utilizar operadores de expansión de parámetros de shell:

case $filename in
  (*_*_*_*) var2=${filename#*_*_}; var2=${var2%%_*};;
  (*)       var2=;;
esac

Lo que funcionaría mejor si el nombre del archivo no es texto o si la parte que desea extraer termina en un carácter de nueva línea (y también sería más eficiente).

A algunos shells les gustan zsho ksh93tienen operadores más avanzados:

  • zsh:

    dividir _y obtener el tercer campo:

    var2=${"${(@s:_:)filename}"[3]}
    

    Usando las ${var/pattern/replacement}referencias anteriores y (en ese caso, primero desea verificar que la variable contenga al menos 3 guiones bajos o no habrá ninguna sustitución).

    set -o extendedglob
    var2=${filename/(#b)*_*_(*)_*/$match[1]}
    
  • ksh93:

    var2=${filename/*_*_@(*)_*/\1}
    

Respuesta3

@Kusalananda tiene razón, sedes la herramienta incorrecta y no se pueden realizar coincidencias sin avidez. Pero puedes usar una solución para las coincidencias no codiciosas: [^_]*coincidirá con cualquier carácter que no sea_

Entonces en tu caso podrías hacer algo como esto:

printf '%s\n' "$file_name" | sed -e 's/^[^_]*_[^_]*_\([^_]*\).*$/\1/g'

Pero... para su caso de uso, será mejor que utilice otras herramientas...

información relacionada