Извлечь подстроку в Bash

Учитывая имя файла в форме someletters_12345_moreleters.ext, я хочу извлечь 5 цифр и поместить их в переменную.

Итак, чтобы подчеркнуть это, у меня есть имя файла с числом символов х, затем пятизначной последовательностью, окруженной одним подчеркиванием с обеих сторон, а затем другим набором из числа символов х. Я хочу взять 5-значный номер и поместить его в переменную.

Меня очень интересует количество различных способов, которыми это может быть достигнуто.

вопрос задан 9.01.2009
Berek Bryan
4111 репутация

20 ответов


  • 790 рейтинг

    Если x является константой, следующее расширение параметра выполняет извлечение подстроки:

    b=${a:12:5}
    

    , где 12 - это смещение (от нуля), а 5 - это длина

    .

    Если символы подчеркивания вокруг цифр являются единственными на входе, вы можете удалить префикс и суффикс (соответственно) в два этапа:

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

    Если есть и другие подчеркивания, это, вероятно, возможно в любом случае, хотя и более сложно. Если кто-нибудь знает, как выполнить оба расширения в одном выражении, я бы тоже хотел знать.

    Оба представленных решения являются чисто bash, без порождения процессов, следовательно, очень быстрыми.

    ответ дан JB., с репутацией 25803, 9.01.2009
  • 524 рейтинг

    Использование вырезать :

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

    Более общий:

    INPUT='someletters_12345_moreleters.ext'
    SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
    echo $SUBSTRING
    
    ответ дан FerranB, с репутацией 19290, 9.01.2009
  • 75 рейтинг

    Общее решение, где число может быть где угодно в имени файла, используя первую из таких последовательностей:

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

    Еще одно решение для извлечения именно части переменной:

    number=${filename:offset:length}
    

    Если ваше имя файла всегда имеет формат stuff_digits_..., вы можете использовать awk:

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

    Еще одно решение, чтобы удалить все, кроме цифр, используйте

    number=$(echo $filename | tr -cd '[[:digit:]]')
    
    ответ дан Johannes Schaub - litb, с репутацией 398434, 9.01.2009
  • 63 рейтинг

    просто попробуйте использовать cut -c startIndx-stopIndx

    ответ дан brown.2179, с репутацией 914, 22.09.2010
  • 31 рейтинг

    Если кто-то хочет получить более точную информацию, вы также можете найти ее в man bash, например,

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

    Результат:

    ${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.
    
    ответ дан jperelli, с репутацией 4463, 31.05.2013
  • 17 рейтинг

    Опираясь на ответ Джора (который не работает для меня):

    substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
    
    ответ дан PEZ, с репутацией 13334, 9.01.2009
  • 15 рейтинг

    Я удивлен, что это чистое решение bash не подошло:

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

    Вы, вероятно, хотите сбросить IFS до того значения, которое было раньше, или unset IFS после этого!

    ответ дан user1338062, с репутацией 6626, 3.06.2013
  • 11 рейтинг

    В соответствии с требованиями

    У меня есть имя файла с х количество символов, а затем пять цифр последовательность окружена одним подчеркиванием с обеих сторон, а затем с другой набор х количество символов. Я хочу взять 5-значный номер и положить это в переменную.

    Я нашел несколько grep способов, которые могут быть полезны:

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

    или лучше

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

    А затем с синтаксисом -Po:

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

    Или, если вы хотите, чтобы в нем поместились ровно 5 символов:

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

    Наконец, чтобы сохранить его в переменной, достаточно использовать синтаксис var=$(command).

    ответ дан fedorqui, с репутацией 158400, 26.06.2013
  • 9 рейтинг

    Без каких-либо подпроцессов вы можете:

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

    Очень маленький вариант этого также будет работать в ksh93.

    ответ дан Darron, с репутацией 18701, 9.01.2009
  • 9 рейтинг

    Если мы сосредоточимся на концепции:
    «Набор из (одной или нескольких) цифр»

    Мы могли бы использовать несколько внешних инструментов для извлечения чисел.
    Мы могли бы довольно легко стереть все остальные символы, или sed или tr:

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

    Но если $ name содержит несколько серий чисел, вышеописанное завершится ошибкой:

    Если "name = someletters_12345_moreleters_323_end. ext ", тогда:

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

    Нам нужно использовать регулярные выражения (регулярное выражение).
    Чтобы выбрать только первый запуск (12345, а не 323) в sed и perl:

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

    Но мы могли бы также сделать это непосредственно в bash (1) :

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

    Это позволяет нам извлечь ПЕРВЫЙ набор цифр любой длины
    в окружении любого другого текста / символов.

    Примечание : regex=[^0-9]*([0-9]{5,5}).*$; будет соответствовать только точно 5 разрядов. :-)

    (1) : быстрее, чем вызов внешнего инструмента для каждого короткого текста. Не быстрее, чем вся обработка внутри sed или awk для больших файлов.

    ответ дан russell, с репутацией 410, 5.08.2014
  • 8 рейтинг

    Вот решение с префиксом-суффиксом (аналогично решениям, данным JB и Darron), которое соответствует первому блоку цифр и не зависит от окружающих подчеркиваний:

    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
    
    ответ дан codist, с репутацией 89, 6.05.2011
  • 7 рейтинг

    Вот как бы я это сделал:

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

    Примечание: приведенное выше является регулярным выражением и ограничено вашим конкретным сценарием из пяти цифр, окруженных подчеркиванием. Измените регулярное выражение, если вам нужно другое соответствие.

    ответ дан nicerobot, с репутацией 7004, 12.01.2009
  • 4 рейтинг

    Данный тест. TXT представляет собой файл, содержащий «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
    
    ответ дан Rick Osman, с репутацией 41, 14.08.2016
  • 4 рейтинг

    Мне нравится способность sed иметь дело с группами регулярных выражений:

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

    Несколько более общим вариантом было бы , а не , чтобы предположить, что у вас есть подчеркивание _, обозначающее начало вашей последовательности цифр, следовательно, например, удаляя все не числа, которые вы получили перед вашей последовательностью: 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.
    

    Подробнее об этом, если вы не слишком уверены в регулярных выражениях:

    • s для _s_ubstitute
    • [0-9]+ соответствует 1+ цифрам
    • \1 ссылки на группу n. 1 из регулярных выражений (группа 0 - это полное совпадение, группа 1 - это совпадение в скобках в этом случае)
    • p флаг для _p_rinting

    Все экранирования \ предназначены для выполнения обработки регулярного выражения sed.

    ответ дан Campa, с репутацией 1730, 21.10.2016
  • 3 рейтинг

    аналогично substr ('abcdefg', 2-1, 3) в php:

    echo 'abcdefg'|tail -c +2|head -c 3
    
    ответ дан diyism, с репутацией 8258, 26.06.2013
  • 3 рейтинг

    Мой ответ будет иметь больше контроля над тем, что вы хотите от вашей строки. Вот код о том, как вы можете извлечь 12345 из вашей строки

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

    Это будет более эффективно, если вы хотите извлечь что-либо, имеющее любые символы, например abc, или любые специальные символы, такие как _ или -. Например: если ваша строка такая, и вы хотите все, что после someletters_ и до _moreleters.ext:

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

    С моим кодом вы можете указать, что именно вы хотите. Объяснение:

    #* Он удалит предыдущую строку, включая соответствующий ключ. Здесь ключ, который мы упомянули, _ % Это удалит следующую строку, включая соответствующий ключ. Здесь ключ, который мы упомянули, '_more *'

    Проведите несколько экспериментов самостоятельно, и вы найдете это интересным.

    ответ дан Alex Raj Kaliamoorthy, с репутацией 822, 29.07.2016
  • 2 рейтинг

    Есть также команда bash buildin 'expr':

    INPUT="someletters_12345_moreleters.ext"  
    SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
    echo $SUBSTRING
    
    ответ дан jor, с репутацией 683, 9.01.2009
  • 2 рейтинг

    Хорошо, здесь идет чистая подстановка параметров с пустой строкой. Предостережение заключается в том, что я определил писем и писем только как символы. Если они буквенно-цифровые, это не будет работать, как есть.

    filename=someletters_12345_moreletters.ext
    substring=${filename//@(+([a-z])_|_+([a-z]).*)}
    echo $substring
    12345
    
    ответ дан morbeo, с репутацией 31, 26.10.2015
  • 1 рейтинг

    Немного поздно, но я просто наткнулся на эту проблему и обнаружил следующее:

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

    Я использовал его, чтобы получить разрешение в миллисекундах во встроенной системе, в которой нет% N на дату:

    set `grep "now at" /proc/timer_list`
    nano=$3
    fraction=`expr $nano : '.*\(...\)......'`
    $debug nano is $nano, fraction is $fraction
    
    ответ дан russell, с репутацией 410, 1.08.2013
  • 1 рейтинг

    Решение Bash:

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

    Это забьет переменную с именем x. Вар x может быть изменен на вар _.

    input='someletters_12345_moreleters.ext'
    IFS="_" read -r _ digs _ <<<"$input"
    
    ответ дан morbeo, с репутацией 31, 22.01.2016