Extract substring in Bash


Dado un nombre de archivo en la forma 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 solo subrayado a cada lado luego otro conjunto de x número de caracteres. Quiero tomar el número de 5 dígitos y ponerlo en una variable.

Estoy muy interesado en el número de diferentes maneras en que esto se puede lograr.

Author: codeforester, 2009-01-09

20 answers

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
 528
Author: FerranB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-04 22:51:55

Si x es constante, el siguiente parámetro de expansión 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 guiones bajos, probablemente sea factible de todos modos, aunque más complicado. Si alguien sabe cómo realizar ambas expansiones en un una sola expresión, me gustaría saber también.

Ambas soluciones presentadas son bash puro, sin proceso de desove involucrado, por lo tanto muy rápido.

 792
Author: JB.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-12 17:54:17

Solución genérica donde el número puede estar en cualquier lugar del nombre del archivo, utilizando la primera de estas 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 para eliminar todo excepto dígitos, use

number=$(echo $filename | tr -cd '[[:digit:]]')
 75
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-09 14:25:34

Solo intenta usar cut -c startIndx-stopIndx

 63
Author: brown.2179,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-22 17:54:15

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:

${parameter:offset}
       ${parameter:offset:length}
              Substring Expansion.  Expands to  up  to  length  characters  of
              parameter  starting  at  the  character specified by offset.  If
              length is omitted, expands to the substring of parameter  start‐
              ing at the character specified by offset.  length and offset are
              arithmetic expressions (see ARITHMETIC  EVALUATION  below).   If
              offset  evaluates  to a number less than zero, the value is used
              as an offset from the end of the value of parameter.  Arithmetic
              expressions  starting  with  a - must be separated by whitespace
              from the preceding : to be distinguished from  the  Use  Default
              Values  expansion.   If  length  evaluates to a number less than
              zero, and parameter is not @ and not an indexed  or  associative
              array,  it is interpreted as an offset from the end of the value
              of parameter rather than a number of characters, and the  expan‐
              sion is the characters between the two offsets.  If parameter is
              @, the result is length positional parameters beginning at  off‐
              set.   If parameter is an indexed array name subscripted by @ or
              *, the result is the length members of the array beginning  with
              ${parameter[offset]}.   A  negative  offset is taken relative to
              one greater than the maximum index of the specified array.  Sub‐
              string  expansion applied to an associative array produces unde‐
              fined results.  Note that a negative offset  must  be  separated
              from  the  colon  by  at least one space to avoid being confused
              with the :- expansion.  Substring indexing is zero-based  unless
              the  positional  parameters are used, in which case the indexing
              starts at 1 by default.  If offset  is  0,  and  the  positional
              parameters are used, $0 is prefixed to the list.
 31
Author: jperelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-22 15:16:02

Basándose en la respuesta de jor (que no funciona para mí):

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
 17
Author: PEZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-09 15:46:32

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

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

Es probable que desee restablecer IFS a qué valor era antes, o unset IFS después!

 15
Author: user1338062,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-03 17:34:40

Siguiendo los requisitos

Tengo un nombre de archivo con x número de caracteres y luego cinco dígitos secuencia rodeada por un solo subrayado a cada lado y luego por otro conjunto de x número de caracteres. Quiero tomar el número de 5 dígitos y pon eso en una variable.

Encontré algunas grep formas 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 -Po sintaxis:

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

O si quieres que se ajuste exactamente 5 personajes:

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

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

 11
Author: fedorqui,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-29 11:50:27

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.

 9
Author: Darron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-09 16:13:38

Si nos enfocamos en el concepto de:
"Una carrera 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, ya sea 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 name contiene varias series de números, lo anterior fallará:

If "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 serie de dígitos de cualquier longitud
rodeado de cualquier otro texto/caracteres.

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

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

 9
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-22 04:25:34

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 circundantes:

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
 8
Author: codist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-06 12:50:13

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 rodeados de guiones bajos. Cambie la expresión regular si necesita una coincidencia diferente.

 7
Author: nicerobot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-12 19:43:20

Me encanta la capacidad de sed para tratar con grupos de expresiones regulares:

> 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 para asumir que tiene un subrayado _ marcando el inicio de su secuencia de dígitos, por lo tanto, por ejemplo, eliminando 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 demasiado seguro con las expresiones regulares:

  • s es para _s_ubstitute
  • [0-9]+ coincidencias 1+ dígitos
  • \1 enlaces al grupo n. 1 de la salida regex (grupo 0 es la coincidencia completa, grupo 1 es la coincidencia entre paréntesis en este caso)
  • p la bandera es para _p_rinting

Todos los escapes \ están allí para hacer que el procesamiento de expresiones regulares de sed funcione.

 4
Author: Campa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-21 07:22:42

Dado la prueba.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
 4
Author: Rick Osman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-23 14:21:19

Similar a substr ('abcdefg', 2-1, 3) en php:

echo 'abcdefg'|tail -c +2|head -c 3
 3
Author: diyism,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-26 11:34:08

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

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

Esto será más eficiente si se quiere extraer algo que tiene caracteres como abc o caracteres especiales como _ o -. Por ejemplo: Si tu cadena es así y quieres todo lo que está despué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 exactamente querer. Explicación:

#* 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 *'

Haz algunos experimentos tú mismo y encontrarás esto interesante.

 3
Author: Alex Raj Kaliamoorthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-29 07:41:26

También está el comando 'expr' de bash:

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING
 2
Author: jor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-09 16:05:52

Ok, aquí va la sustitución pura de parámetros con una cadena vacía. Advertencia es que he definido someletters y moreletters como sólo los personajes. Si son alfanuméricos, esto no funcionará como es.

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
 2
Author: morbeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-26 18:04:06

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 utilicé para obtener una resolución de milisegundos en un sistema embebido 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
 1
Author: russell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-01 08:12:33

Una solución bash:

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

Esto golpeará una variable llamada x. El var x podría cambiarse por el var _.

input='someletters_12345_moreleters.ext'
IFS="_" read -r _ digs _ <<<"$input"
 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-22 05:45:24