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

396

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

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

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

  • 4
    Ответ JB явно выигрывает голоса - время менять принятый ответ?
  • 2
    Большинство ответов не отвечают на ваш вопрос, потому что вопрос неоднозначный. «У меня есть имя файла с x количеством символов, затем последовательность из пяти цифр, окруженная одним подчеркиванием с обеих сторон, а затем другой набор из x числа символов» . По этому определению abc_12345_def_67890_ghi_def является допустимым вводом. Что ты хочешь случиться? Давайте предположим, что есть только одна последовательность из 5 цифр. У вас все еще есть abc_def_12345_ghi_jkl или 1234567_12345_1234567 или 12345d_12345_12345e качестве допустимого ввода, основанного на вашем определении ввода, и большинство ответов ниже не справятся с этим.
Теги:
string
substring

20 ответов

397
Лучший ответ

Используйте cut:

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

Более общий:

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
  • 1
    более общий ответ именно то, что я искал, спасибо
  • 53
    Флаг -f принимает индексы, основанные на 1, а не индексы, основанные на 0, к которым привык программист.
Показать ещё 4 комментария
648

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

b=${a:12:5}

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

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

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

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

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

  • 0
    Вы можете сделать оба расширения одновременно: ${${a#*_}%_*} . Я использовал это прежде, чтобы связать воедино несколько операций bash string, чтобы получить определенный раздел подстроки.
  • 15
    @SpencerRathbun bash: ${${a#*_}%_*}: bad substitution в моем GNU bash 4.2.45.
Показать ещё 9 комментариев
68

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

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:]]')
  • 2
    Что делать, если я хочу извлечь цифру / слово из последней строки файла.
41

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

  • 2
    Есть что-то вроде startIndex-lastIndex - 1?
  • 1
    @Niklas В bash, проли startIndx-$((lastIndx-1))
Показать ещё 5 комментариев
27

Если кто-то хочет получить более строгую информацию, вы также можете найти его в 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.
  • 1
    Очень важное предупреждение с отрицательными значениями, как указано выше: арифметические выражения, начинающиеся с -, должны быть отделены пробелом от предыдущего: отличить от расширения Use Default Values. Итак, чтобы получить последние четыре символа переменной var: ${var: -4}
15

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

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

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

  • 1
    это не чисто решение bash, я думаю, что оно работает в чистой оболочке (/ bin / sh)
  • 4
    +1 Вы могли бы написать это другим способом, чтобы избежать необходимости сбрасывать IFS и позиционные параметры: IFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
Показать ещё 1 комментарий
14

Основываясь на jor-ответе (который не работает для меня):

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
  • 10
    Регулярные выражения реальное дело , когда у вас есть что - то сложное и просто подсчет подчеркивания не будет cut его.
11

Следуя требованиям

У меня есть имя файла с числом символов x, затем пять цифр последовательность, окруженная одним подчеркиванием с обеих сторон, затем другая набор из числа символов. Я хочу взять 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).

  • 2
    Я считаю, что в настоящее время нет необходимости использовать egrep, сама команда предупреждает вас: Invocation as 'egrep' is deprecated; use 'grep -E' instead . Я отредактировал твой ответ.
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}).*$; будет соответствовать только пятизначным тиражам.: -)

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

9

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

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

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

7

Здесь префикс-суффиксное решение (похожее на решения, заданные 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
7

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

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

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

  • 1
    Это общий способ, который работает, даже если вам нужно извлечь больше, чем я, как я.
3

Мне нравится 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

Все escape-последовательности \ предназначены для обработки обработки sed regexp.

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 *"

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

3

похож на substr ('abcdefg', 2-1, 3) в php:

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

Данный файл test.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
2

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

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
  • 1
    офигенно но требует как минимум bash v4
2

Также существует команда bash builtin 'expr':

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING
  • 2
    expr не является встроенным.
1

A bash решение:

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

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

input='someletters_12345_moreleters.ext'
IFS="_" read -r _ digs _ <<<"$input"
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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню