Extraer subcadena en Bash

Dado un nombre de archivo con el formato someletters_12345_moreleters.ext , quiero extraer los 5 dígitos y ponerlos en una variable.

Así que para enfatizar el punto, tengo un nombre de archivo con x número de caracteres, luego una secuencia de cinco dígitos rodeada por un único guión bajo a cada lado y luego otro conjunto de x número de caracteres. Quiero tomar el número de 5 dígitos y poner eso en una variable.

Estoy muy interesado en la cantidad de formas diferentes en que esto se puede lograr.

Use corte :

 echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2 

Más genérico:

 INPUT='someletters_12345_moreleters.ext' SUBSTRING=$(echo $INPUT| cut -d'_' -f 2) echo $SUBSTRING 

Si x es constante, la siguiente expansión de parámetros realiza la extracción de subcadenas:

 b=${a:12:5} 

donde 12 es el desplazamiento (basado en cero) y 5 es la longitud

Si los guiones bajos alrededor de los dígitos son los únicos en la entrada, puede quitar el prefijo y el sufijo (respectivamente) en dos pasos:

 tmp=${a#*_} # remove prefix ending in "_" b=${tmp%_*} # remove suffix starting with "_" 

Si hay otros caracteres de subrayado, probablemente sea posible de todos modos, aunque más complicado. Si alguien sabe cómo realizar ambas expansiones en una sola expresión, me gustaría saber también.

Ambas soluciones presentadas son pura bash, sin involucrar el desove del proceso, por lo tanto, muy rápido.

Solución genérica donde el número puede estar en cualquier parte del nombre de archivo, usando la primera de tales secuencias:

 number=$(echo $filename | egrep -o '[[:digit:]]{5}' | head -n1) 

Otra solución para extraer exactamente una parte de una variable:

 number=${filename:offset:length} 

Si su nombre de archivo siempre tiene el formato stuff_digits_... puede usar awk:

 number=$(echo $filename | awk -F _ '{ print $2 }') 

Otra solución más para eliminar todo, excepto los dígitos, usar

 number=$(echo $filename | tr -cd '[[:digit:]]') 

solo intente usar cut -c startIndx-stopIndx

En caso de que alguien quiera información más rigurosa, también puede buscarla en man bash como este

 $ man bash [press return key] /substring [press return key] [press "n" key] [press "n" key] [press "n" key] [press "n" key] 

Resultado:

 $ {parámetro: desplazamiento}
        $ {parámetro: desplazamiento: longitud}
               Expansión de subcadenas  Se expande a caracteres de hasta longitud de
               parámetro que comienza en el carácter especificado por desplazamiento.  Si
               se omite la longitud, se expande a la subcadena del inicio del parámetro
               ing en el carácter especificado por desplazamiento.  longitud y desplazamiento son
               expresiones aritméticas (ver EVALUACIÓN ARITMÉTICA abajo).  Si
               offset evalúa a un número menor que cero, se usa el valor
               como un desplazamiento desde el final del valor del parámetro.  Aritmética
               expresiones que comienzan con a - deben estar separadas por espacios en blanco
               de lo anterior: para ser distinguido del uso predeterminado
               Expansión de valores.  Si la longitud se evalúa en un número menor que
               cero, y el parámetro no es @ ni indexado ni asociativo
               array, se interpreta como un desplazamiento desde el final del valor
               del parámetro en lugar de una cantidad de caracteres, y la expansión
               sion es los personajes entre las dos compensaciones.  Si el parámetro es
               @, el resultado es parámetros posicionales de longitud que comienzan en off-
               conjunto.  Si el parámetro es un nombre de matriz indexado con subíndices @ o
               *, el resultado es la longitud de los miembros de la matriz que comienza con
               $ {parámetro [desplazamiento]}.  Se toma una compensación negativa relativa a
               uno mayor que el índice máximo de la matriz especificada.  Sub-
               la expansión de cadena aplicada a una matriz asociativa produce
               resultados multados  Tenga en cuenta que una compensación negativa debe estar separada
               desde el colon por al menos un espacio para evitar ser confundido
               con la: - expansión.  La indexación de subcadenas no se basa en cero
               los parámetros posicionales son usados, en cuyo caso la indexación
               comienza en 1 por defecto.  Si el desplazamiento es 0, y el posicional
               se usan parámetros, $ 0 tiene el prefijo en la lista.

Sobre la base de la respuesta de jor (que no funciona para mí):

 substring=$(expr "$filename" : '.*_\([^_]*\)_.*') 

Me sorprende que esta solución pura bash no haya surgido:

 a="someletters_12345_moreleters.ext" IFS="_" set $a echo $2 # prints 12345 

¡Es probable que desee restablecer IFS al valor que tenía antes, o unset IFS después!

Siguiendo los requisitos

Tengo un nombre de archivo con x número de caracteres, luego una secuencia de cinco dígitos rodeada por un único guión bajo a cada lado y luego otro conjunto de x número de caracteres. Quiero tomar el número de 5 dígitos y poner eso en una variable.

Encontré algunas formas grep que pueden ser útiles:

 $ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]+" 12345 

o mejor

 $ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]{5}" 12345 

Y luego con la syntax -Po :

 $ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+' 12345 

O si desea que quepa exactamente 5 caracteres:

 $ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}' 12345 

Finalmente, para hacer que se almacene en una variable solo es necesario usar la syntax var=$(command) .

Sin ningún subproceso, puede:

 shopt -s extglob front=${input%%_+([a-zA-Z]).*} digits=${front##+([a-zA-Z])_} 

Una variante muy pequeña de esto también funcionará en ksh93.

Si nos enfocamos en el concepto de:
“Una corrida de (uno o varios) dígitos”

Podríamos usar varias herramientas externas para extraer los números.
Podríamos borrar fácilmente todos los demás caracteres, sed o tr:

 name='someletters_12345_moreleters.ext' echo $name | sed 's/[^0-9]*//g' # 12345 echo $name | tr -c -d 0-9 # 12345 

Pero si $ name contiene varias ejecuciones de números, lo anterior fallará:

Si “name = someletters_12345_moreleters_323_end.ext”, entonces:

 echo $name | sed 's/[^0-9]*//g' # 12345323 echo $name | tr -c -d 0-9 # 12345323 

Necesitamos usar expresiones regulares (regex).
Para seleccionar solo la primera ejecución (12345 no 323) en sed y perl:

 echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$/\1/' perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print "$num\n";' 

Pero también podríamos hacerlo directamente en bash (1) :

 regex=[^0-9]*([0-9]{1,}).*$; \ [[ $name =~ $regex ]] && echo ${BASH_REMATCH[1]} 

Esto nos permite extraer la PRIMERA ejecución de dígitos de cualquier longitud
rodeado de cualquier otro texto / caracteres.

Nota : regex=[^0-9]*([0-9]{5,5}).*$; coincidirá exactamente con solo 5 dígitos. 🙂

(1) : más rápido que llamar a una herramienta externa para cada texto breve. No es más rápido que hacer todo el procesamiento dentro de sed o awk para archivos grandes.

Aquí hay una solución prefijo-sufijo (similar a las soluciones dadas por JB y Darron) que coincide con el primer bloque de dígitos y no depende de los guiones bajos que lo rodean:

 str='someletters_12345_morele34ters.ext' s1="${str#"${str%%[[:digit:]]*}"}" # strip off non-digit prefix from str s2="${s1%%[^[:digit:]]*}" # strip off non-digit suffix from s1 echo "$s2" # 12345 

Así es como lo haría:

 FN=someletters_12345_moreleters.ext [[ $FN =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]} 

Nota: lo anterior es una expresión regular y está restringido a su escenario específico de cinco dígitos rodeado de guiones bajos. Cambie la expresión regular si necesita una coincidencia diferente.

Me encanta la capacidad de sed para tratar con grupos regex:

 > var="someletters_12345_moreletters.ext" > digits=$( echo $var | sed "s/.*_\([0-9]\+\).*/\1/p" -n ) > echo $digits 12345 

Una opción un poco más general sería no asumir que tiene un guión bajo _ marcando el comienzo de la secuencia de dígitos, por lo tanto, por ejemplo, quitando todos los no-números que obtiene antes de su secuencia: s/[^0-9]\+\([0-9]\+\).*/\1/p .


 > man sed | grep s/regexp/replacement -A 2 s/regexp/replacement/ Attempt to match regexp against the pattern space. If successful, replace that portion matched with replacement. The replacement may contain the special character & to refer to that portion of the pattern space which matched, and the special escapes \1 through \9 to refer to the corresponding matching sub-expressions in the regexp. 

Más sobre esto, en caso de que no estés muy seguro con las expresiones regulares:

  • s es para _substituto
  • [0-9]+ coincide con 1 o más dígitos
  • \1 enlaces al grupo n.1 de la salida de expresiones regulares (el grupo 0 es la coincidencia completa, el grupo 1 es la coincidencia entre paréntesis en este caso)
  • p bandera es para _p_rinting

Todos los escapes \ están ahí para hacer el trabajo de procesamiento de expresiones regulares de sed .

Given test.txt es un archivo que contiene “ABCDEFGHIJKLMNOPQRSTUVWXYZ”

 cut -b19-20 test.txt > test1.txt # This will extract chars 19 & 20 "ST" while read -r; do; > x=$REPLY > done < test1.txt echo $x ST 

similar a substr (‘abcdefg’, 2-1, 3) en php:

 echo 'abcdefg'|tail -c +2|head -c 3 

Mi respuesta tendrá más control sobre lo que quieres de tu cadena. Aquí está el código sobre cómo puedes extraer 12345 de tu cadena

 str="someletters_12345_moreleters.ext" str=${str#*_} str=${str%_more*} echo $str 

Esto será más eficiente si desea extraer algo que tenga caracteres como abc o cualquier carácter especial como _ o - . Por ejemplo: si su cadena es así y quiere todo lo que está detrás de someletters_ y antes de _moreleters.ext :

 str="someletters_123-45-24a&13b-1_moreleters.ext" 

Con mi código puedes mencionar lo que quieres exactamente. Explicación:

#* Se eliminará la cadena anterior, incluida la clave coincidente. Aquí la clave que mencionamos es _ % Eliminará la siguiente cadena, incluida la clave coincidente. Aquí la clave que mencionamos es ‘_more *’

Haga algunos experimentos usted mismo y encontrará esto interesante.

También está el comando bash builtin ‘expr’:

 INPUT="someletters_12345_moreleters.ext" SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' ` echo $SUBSTRING 

Ok, aquí va la Sustitución de Parámetros pura con una cadena vacía. La advertencia es que he definido algunas letras y más letras como únicos caracteres. Si son alfanuméricos, esto no funcionará como está.

 filename=someletters_12345_moreletters.ext substring=${filename//@(+([az])_|_+([az]).*)} echo $substring 12345 

Un poco tarde, pero me encontré con este problema y encontré lo siguiente:

 host:/tmp$ asd=someletters_12345_moreleters.ext host:/tmp$ echo `expr $asd : '.*_\(.*\)_'` 12345 host:/tmp$ 

Lo usé para obtener una resolución de milisegundos en un sistema integrado que no tiene% N para la fecha:

 set `grep "now at" /proc/timer_list` nano=$3 fraction=`expr $nano : '.*\(...\)......'` $debug nano is $nano, fraction is $fraction 

Una solución bash:

 IFS="_" read -rx digs x <<<'someletters_12345_moreleters.ext' 

Esto marcará una variable llamada x . La var x puede cambiarse a var _ .

 input='someletters_12345_moreleters.ext' IFS="_" read -r _ digs _ <<<"$input"