
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 sed
no 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 the
al 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 $variable
se 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 awk
ni sed
harí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_name
no 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 ksh
shell también haría expansiones de llaves en el valor de $file_name
cuando no está entrecomillado.
Respuesta2
Primera nota que sed
es 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 echo
para generar datos arbitrarios, usa printf
en 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 sed
el 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 zsh
o ksh93
tienen 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, sed
es 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...