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

730

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

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

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

Берек Брайан
источник
5
Ответ JB явно выигрывает голоса - время менять принятый ответ?
Джефф
3
Большинство ответов, кажется, не отвечают на ваш вопрос, потому что вопрос неоднозначен. «У меня есть имя файла с x количеством символов, затем последовательность из пяти цифр, окруженная одним подчеркиванием с обеих сторон, а затем другой набор из x числа символов» . По этому определению abc_12345_def_67890_ghi_defдействительный вход. Что ты хочешь случиться? Давайте предположим, что есть только одна последовательность из 5 цифр. У вас все еще есть abc_def_12345_ghi_jklили 1234567_12345_1234567или 12345d_12345_12345eкак действительный ввод, основанный на вашем определении ввода, и большинство ответов ниже не справятся с этим.
мужчина
2
Этот вопрос имеет пример ввода, который является слишком конкретным. Из-за этого он получил много конкретных ответов для этого конкретного случая (только цифры, тот же _разделитель, ввод, который содержит целевую строку только один раз и т. Д.). Лучший (самый общий и самый быстрый) ответ имеет, после 10 лет, только 7 upvotes, в то время как другие ограниченные ответы сотни. Заставляет меня терять веру в разработчиков 😞
Дан Даскалеску

Ответы:

694

Используйте вырезать :

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

Более общий:

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
FerranB
источник
1
более общий ответ именно то, что я искал, спасибо
Берек Брайан
71
Флаг -f принимает индексы на основе 1, а не индексы на основе 0, к которым привык бы программист.
Мэтью Дж
2
INPUT = someletters_12345_moreleters.ext SUBSTRING = $ (echo $ INPUT | cut -d'_ '-f 2) echo $ SUBSTRING
мани Дипак
3
Вы должны правильно использовать двойные кавычки вокруг аргументов, echoесли только вы не уверены, что переменные не могут содержать нерегулярные пробелы или метасимволы оболочки. См. Подробнее stackoverflow.com/questions/10067266/…
tripleee
Число '2' после '-f' указывает оболочке извлечь 2-й набор подстрок.
Сандун
1088

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

b=${a:12:5}

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

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

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

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

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

JB.
источник
18
@SpencerRathbun bash: ${${a#*_}%_*}: bad substitutionна моем GNU bash 4.2.45.
JB.
2
@jonnyB, некоторое время в прошлом это работало. Мои коллеги сказали мне, что это прекратилось, и они изменили это на команду sed или что-то в этом роде. Глядя на это в истории, я запускал его в shсценарии, который, вероятно, был чертой. На данный момент я не могу заставить его работать больше.
Спенсер Рэтбун
22
JB, вы должны уточнить, что «12» - это смещение (от нуля), а «5» - это длина. Также +1 за ссылку @gontard, в которой все изложено!
Доктор J
1
При запуске этого скрипта под именем «sh run.sh» может возникнуть ошибка Bad Substitution. Чтобы избежать этого, измените разрешения для run.sh (chmod + x run.sh), а затем запустите скрипт как «./run.sh»
Ankur
2
Кстати, параметр смещения также может быть отрицательным. Вам просто нужно позаботиться о том, чтобы не приклеить его к двоеточию, иначе bash интерпретирует его как :-замену «Использовать значения по умолчанию». Таким образом, ${a: -12:5}получается 5 символов 12 символов от конца и ${a: -12:-5}7 символов между концом-12 и концом-5.
JB.
97

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

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
Что делать, если я хочу извлечь цифру / слово из последней строки файла.
Сахра
93

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

brown.2179
источник
2
Есть что-то вроде startIndex-lastIndex - 1?
Никлас
1
@Niklas In bash, проли startIndx-$((lastIndx-1))
коричневый.2179
3
start=5;stop=9; echo "the rain in spain" | cut -c $start-$(($stop-1))
коричневый.2179
1
Проблема в том, что ввод динамический, так как я также использую канал, чтобы получить его, так что это в основном. git log --oneline | head -1 | cut -c 9-(end -1)
Никлас
Это можно сделать с помощью cut, если разбить на две части как line=git log --oneline | head -1` && echo $ line | cut -c 9 - $ (($ {# line} -1)) `, но в данном конкретном случае лучше использовать sed asgit log --oneline | head -1 | sed -e 's/^[a-z0-9]* //g'
brown.2179
34

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

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

Результат:

$ {Параметр: смещение}
       $ {Параметр: смещение: длина}
              Расширение подстроки. Расширяется до длины символов
              параметр, начинающийся с символа, указанного смещением. Если
              длина опущена, расширяется до подстроки параметра start-
              в символ, указанный смещением. длина и смещение
              арифметические выражения (см. АРИФМЕТИЧЕСКУЮ ОЦЕНКУ ниже). Если
              смещение оценивается как число меньше нуля, используется значение
              как смещение от конца значения параметра. арифметика
              выражения, начинающиеся с - должны быть разделены пробелами
              от предыдущего: отличить от использования по умолчанию
              Расширение ценностей. Если длина оценивается как число меньше
              ноль, а параметр не является @ и не является индексированным или ассоциативным
              массив, он интерпретируется как смещение от конца значения
              параметра, а не количество символов, и расширение
              sion - символы между двумя смещениями. Если параметр
              @, результат - позиционные параметры длины, начинающиеся с
              набор. Если параметр является индексированным именем массива, подписанным @ или
              *, результатом является длина членов массива, начинающаяся с
              $ {Параметр [смещение]}. Отрицательное смещение берется относительно
              на единицу больше максимального индекса указанного массива. под-
              расширение строки, примененное к ассоциативному массиву, приводит к
              оштрафованные результаты. Обратите внимание, что отрицательное смещение должно быть отделено
              из толстой кишки, по крайней мере, на один пробел, чтобы избежать путаницы
              с: - расширением. Индексирование подстроки начинается с нуля, если
              используются позиционные параметры, в этом случае индексация
              начинается с 1 по умолчанию. Если смещение равно 0, а позиционное
              параметры используются, $ 0 является префиксом к списку.
jperelli
источник
2
Очень важное предостережение с отрицательными значениями, как указано выше: арифметические выражения, начинающиеся с -, должны отделяться пробелом от предыдущего: отличать от расширения Use Default Values. Итак, чтобы получить последние четыре символа ${var: -4}
переменной
26

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

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

Объяснение:

Bash-конкретно:

Регулярные выражения (RE): _([[:digit:]]{5})_

  • _ литералы для разграничения / привязки границ сопоставления для сопоставляемой строки
  • () создать группу захвата
  • [[:digit:]] это класс персонажей, я думаю, это говорит само за себя
  • {5} означает, что ровно пять из предшествующего символа, класса (как в этом примере) или группы должны совпадать

В английском языке вы можете думать, что он ведет себя так: FNстрока повторяется символ за символом, пока мы не увидим, _в какой момент группа захвата открыта, и мы не попытаемся сопоставить пять цифр. Если это сопоставление прошло успешно, группа захвата сохраняет пять пройденных цифр. Если следующий символ - _, условие выполнено успешно, группа захвата становится доступной BASH_REMATCH, и NUM=может выполняться следующий оператор. В случае сбоя какой-либо части сопоставления сохраненные данные удаляются, а посимвольная обработка продолжается после _. например, если FNгде _1 _12 _123 _1234 _12345_, было бы четыре фальстарта, прежде чем он нашел совпадение.

nicerobot
источник
3
Это общий способ, который работает, даже если вам нужно извлечь больше, чем я, как я.
zebediah49
3
Это самый общий ответ, и его следует принять. Он работает для регулярного выражения, а не только для строки символов в фиксированной позиции или между одним и тем же разделителем (который разрешает cut). Это также не зависит от выполнения внешней команды.
Дан Даскалеску
1
Этот ответ преступно недооценен.
chepner
Это замечательно! Я приспособил это, чтобы использовать различные стартовые / стоповые дилиметры (замените _) и числами переменной длины (. Для {5}) для моей ситуации. Может кто-нибудь сломать эту черную магию и объяснить это?
Пол
1
@Paul Я добавил больше деталей к своему ответу. Надеюсь, это поможет.
nicerobot
21

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

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

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

user1338062
источник
1
это не чисто решение bash, я думаю, что оно работает в чистой оболочке (/ bin / sh)
kayn
5
+1 Вы могли бы написать это другим способом, чтобы избежать необходимости сбрасывать IFSи позиционные параметры:IFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
Кодзиро
2
Это зависит от расширения пути! (так что он сломан).
gniourf_gniourf
20

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

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

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

У меня есть имя файла с x количеством символов, затем последовательность из пяти цифр, окруженная одним подчеркиванием с обеих сторон, а затем другой набор из 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
Я считаю , что в настоящее время нет необходимости использовать задать расширенные , сама команда предупреждает вас: Invocation as 'egrep' is deprecated; use 'grep -E' instead. Я отредактировал твой ответ.
Нейротрансмиттер
11

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

Мы могли бы использовать несколько внешних инструментов для извлечения чисел.
Мы могли бы легко стереть все другие символы, как 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 для больших файлов.


источник
10

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

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

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

Darron
источник
9

Вот решение с префиксом-суффиксом (аналогично решениям 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
источник
7

Мне нравится 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 ссылки на группу №1 вывода регулярного выражения (группа 0 - это полное совпадение, группа 1 - это совпадение в скобках в этом случае)
  • p флаг для _p_rinting

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

Campa
источник
6

Мой ответ будет иметь больше контроля над тем, что вы хотите от вашей строки. Вот код о том, как вы можете извлечь 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 *»

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

Алекс Радж Калиамурти
источник
6

Данный 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
Рик Осман
источник
Это чрезвычайно специфично для этого конкретного входа. Единственное общее решение общего вопроса (которое ОП должен был задать) - это использовать регулярное выражение .
Дан Даскалеску
3

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

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

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

echo 'abcdefg'|tail -c +2|head -c 3
diyism
источник
Это очень специфично для этого входа. Единственное общее решение общего вопроса (которое ОП должен был задать) - это использовать регулярное выражение .
Дан Даскалеску
1

Также есть встроенная команда bash expr:

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING
Джор
источник
4
exprне является встроенным
gniourf_gniourf
1
Это также не обязательно в свете =~оператора, поддерживаемого [[.
chepner
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
Рассел
источник
1

Решение 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

Инклюзивный конец, похожий на реализации JS и Java. Удалить +1, если вы не хотите этого.

substring() {
    local str="$1" start="${2}" end="${3}"

    if [[ "$start" == "" ]]; then start="0"; fi
    if [[ "$end"   == "" ]]; then end="${#str}"; fi

    local length="((${end}-${start}+1))"

    echo "${str:${start}:${length}}"
} 

Пример:

    substring 01234 0
    01234
    substring 012345 0
    012345
    substring 012345 0 0
    0
    substring 012345 1 1
    1
    substring 012345 1 2
    12
    substring 012345 0 1
    01
    substring 012345 0 2
    012
    substring 012345 0 3
    0123
    substring 012345 0 4
    01234
    substring 012345 0 5
    012345

Больше примеров звонков:

    substring 012345 0
    012345
    substring 012345 1
    12345
    substring 012345 2
    2345
    substring 012345 3
    345
    substring 012345 4
    45
    substring 012345 5
    5
    substring 012345 6

    substring 012345 3 5
    345
    substring 012345 3 4
    34
    substring 012345 2 4
    234
    substring 012345 1 3
    123

Добро пожаловать.

ммм
источник