Dividir cadena por delimitador y obtener el elemento N


77

Tengo una cadena:

one_two_three_four_five

Necesito guardar en un Avalor variable twoy en un Bvalor variable fourde la cadena anterior

Respuestas:


109

Use cutcon _como delimitador de campo y obtenga los campos deseados:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

También puedes usar echoy pipe en lugar de Here string:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Ejemplo:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

¿Hay alguna alternativa? Estoy usando ksh (no bsh) y devuelve ksh: error de sintaxis: `<'inesperado
Alex

@Alex Comprueba mis ediciones.
heemayl

Buena respuesta, tengo una pequeña pregunta: ¿qué sucede si su variable "$ s" es una carpeta de ruta. Cuando intento cortar una carpeta de ruta me gusta lo siguiente: `$ FILE = my_user / my_folder / [file] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* ¿Sabes lo que está pasando aquí?
Henry Navarro

1
Y si solo desea el último campo, utilizando solo componentes integrados de shell, sin necesidad de especificar su posición, o cuando no conoce el número de campos:echo "${s##*_}"
Amit Naidu

19

Usando solo construcciones sh POSIX, puede usar construcciones de sustitución de parámetros para analizar un delimitador a la vez. Tenga en cuenta que este código supone que existe el número requerido de campos; de lo contrario, se repite el último campo.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Alternativamente, puede usar una sustitución de parámetro sin comillas con la expansión de comodín deshabilitada y IFSconfigurada en el carácter delimitador (esto solo funciona si el delimitador es un solo carácter que no es un espacio en blanco o si alguna secuencia de espacios en blanco es un delimitador).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Esto cambia los parámetros posicionales. Si hace esto en una función, solo se ven afectados los parámetros posicionales de la función.

Otro enfoque es usar el readincorporado.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

El uso de unset IFSno vuelve IFSal valor predeterminado. Si después de eso alguien OldIFS="$IFS"tiene un valor nulo dentro de OldIFS. Además, se supone que el valor anterior de IFS es el predeterminado, que es muy posible (y útil) no ser. La única solución correcta es almacenar old="$IFS"y luego restaurar con IFS = "$ old". O ... usa un sub-shell (...). O, mejor aún, lea mi respuesta.
sorontar

@sorontar unset IFSno restaura IFSel valor predeterminado, pero devuelve la división de campo al efecto predeterminado. Sí, es una limitación, pero generalmente es aceptable en la práctica. El problema con una subshell es que necesitamos extraer datos de ella. Muestro una solución que no cambia el estado al final, con read. (Funciona en shells POSIX, pero IIRC no en el shell Bourne porque se ejecutaría readen un subshell debido al documento here.) El uso <<<como en su respuesta es una variante que funciona solo en ksh / bash / zsh.
Gilles 'SO- deja de ser malvado'

No veo un problema incluso con att o heirloom shell sobre un subshell. Todos los shells probados (incluido el bourne antiguo) proporcionan el valor correcto en el shell principal.
sorontar

¿Qué pasa si mi camino es algo así user/my_folder/[this_is_my_file]*? Lo que obtengo cuando sigo estos pasos es[this_is_my_file]*
Henry Navarro

@HenryNavarro Esta salida no corresponde a ninguno de los fragmentos de código en mi respuesta. Ninguno de ellos hace nada especial /.
Gilles 'SO- deja de ser malvado'

17

Quería ver una awkrespuesta, así que aquí hay una:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

1
Y si desea la última pieza, sin necesidad de especificar su posición o cuando no conoce el número de campos:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu

8

La forma más simple (para conchas con <<<) es:

 IFS='_' read -r a second a fourth a <<<"$string"

Usando una variable temporal en $alugar de $_porque un shell se queja.

En un guión completo:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

No cambia IFS, no hay problemas con set -f(expansión de nombre de ruta) No hay cambios en los parámetros posicionales ("$ @")


Para una solución portátil a todos los shells (sí, todos los POSIX incluidos) sin cambiar IFS o set -f, use el equivalente de heredoc (un poco más complejo):

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Comprenda que estas soluciones (tanto el documento here-doc como el uso de <<<eliminarán todas las nuevas líneas finales.
Y que esto está diseñado para un contenido variable de "una línea". Las
soluciones para líneas múltiples son posibles pero necesitan construcciones más complejas.


Una solución muy simple es posible en bash versión 4.4

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

No hay un equivalente para los shells POSIX, ya que muchos shells POSIX no tienen arrays.

Para los shells que tienen arrays pueden ser tan simples como:
(probado trabajando en attsh, lksh, mksh, ksh y bash)

set -f; IFS=_; arr=($string)

Pero con muchas tuberías adicionales para mantener y restablecer variables y opciones:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

En zsh, las matrices comienzan en 1 y no divide la cadena de forma predeterminada.
Por lo tanto, se deben hacer algunos cambios para que esto funcione en zsh.


las soluciones que usan read son simples siempre que OP no quiera extraer los elementos 76 y 127 de una cadena larga ...
don_crissti

@don_crissti Bueno, sí, por supuesto, pero una construcción similar: readarraypodría ser más fácil de usar para esa situación.
sorontar

@don_crissti También agregué una solución de matriz para shells que tienen matrices. Para los shells POSIX, bueno, al no tener arrays, los parámetros posicionales de hasta 127 elementos no son una solución "simple" en ninguna medida.
sorontar

2

Con zshusted podría dividir la cadena (en _) en una matriz:

elements=(${(s:_:)string})

y luego acceda a cada elemento mediante el índice de matriz:

print -r ${elements[4]}

Tenga en cuenta que los índices de matrizzsh (a diferencia de ksh/ bash) comienzan en 1 .


Recuerde agregar set -fadvertencia a la primera solución. ... asteriscos *tal vez?
sorontar

@sorontar: ¿por qué crees que lo necesito set -f? No estoy usando read/ IFS. Pruebe mis soluciones con una cadena como *_*_*o lo que sea ...
don_crissti

No para zsh, pero el usuario solicitó una solución ksh, por lo que puede intentar usarla en ese shell. Una advertencia lo ayudará a evitar el problema.
sorontar

1

¿Se permite una solución de Python?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four

No. mal mal answet
Raj Kumar

0

Otro ejemplo awk; Más simple de entender.

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Se puede usar con variables también.
Supongamos:
this_str = "one_two_three_four_five"
Entonces lo siguiente funciona:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' `
B =` echo $ {this_str} | awk -F_ '{print $ 2}' '
C = `echo $ {this_str} | awk -F_ '{print $ 3}' '
... y así sucesivamente ...

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.