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.
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
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.
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:]]')
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
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.
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" : '.*_\([^_]*\)_.*')
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!
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)
.
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.
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.
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
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.
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.
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
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
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.
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
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
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
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"