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

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

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

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

Похоже, что большинство ответов не отвечают на ваш вопрос, потому что вопрос неоднозначен. «У меня есть имя файла с количеством символов x, затем пятизначная последовательность, окруженная одним подчеркиванием с обеих сторон, затем еще один набор с количеством символов x». По этому определению abc_12345_def_67890_ghi_def является допустимым входом. Чего ты хочешь? Предположим, есть только одна последовательность из 5 цифр. У вас все еще есть abc_def_12345_ghi_jkl, 1234567_12345_1234567 или 12345d_12345_12345e в качестве допустимого ввода на основе вашего определения ввода, и большинство ответов ниже не справятся с этим.

gman 20.04.2018 07:42

В этом вопросе есть слишком конкретный пример ввода. Из-за этого он получил много конкретных ответов для этот конкретный случай (только цифры, тот же разделитель _, ввод, который содержит целевую строку только один раз и т. д.). лучший (самый общий и самый быстрый) ответ за 10 лет набрал только 7 голосов, в то время как другие ограниченные ответы получили сотни. Заставляет потерять веру в разработчиков ?

Dan Dascalescu 08.05.2019 21:30

Заголовок кликбейта. Значение функции подстроки хорошо известно и означает получение детали по числовым позициям. Все остальное (indexOf, regex) касается поиска. На вопрос, который был задан на 3 месяца раньше, о подстроке в bash, был дан тот же ответ, но без «подстроки» в заголовке. Не вводит в заблуждение, но не назван должным образом. Результаты: ответ о встроенной функции в вопросе с наибольшим количеством голосов похоронен на 5 экранов с сортировкой активности; старый и более точный вопрос, отмечен как повторяющийся. stackoverflow.com/questions/219402/…

user9999 23.10.2020 14:04
В чем разница между методом "==" и equals()
В чем разница между методом "==" и equals()
Это один из наиболее часто задаваемых вопросов новичкам на собеседовании. Давайте обсудим его на примере.
Замена символа по определенному индексу в JavaScript
Замена символа по определенному индексу в JavaScript
В JavaScript существует несколько способов заменить символ в строке по определенному индексу.
794
3
1 324 250
22
Перейти к ответу Данный вопрос помечен как решенный

Ответы 22

Ответ принят как подходящий

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

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

Более общий:

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING

более общий ответ - это именно то, что я искал, спасибо

Berek Bryan 09.01.2009 17:00

Флаг -f принимает индексы, начинающиеся с 1, а не индексы с отсчетом от 0, к которым привык бы программист.

Matthew G 23.07.2013 04:49

INPUT = someletters_12345_moreleters.ext SUBSTRING = $ (echo $ INPUT | cut -d'_ '-f 2) echo $ SUBSTRING

mani deepak 24.03.2014 14:29

Вы должны правильно использовать двойные кавычки вокруг аргументов echo, если вы точно не знаете, что переменные не могут содержать нерегулярные пробелы или метасимволы оболочки. См. Далее stackoverflow.com/questions/10067266/…

tripleee 24.01.2017 12:30

Число «2» после «-f» указывает оболочке извлечь второй набор подстроки.

Sandun 10.07.2018 16:42

Может это глупый вопрос, но почему не работает SUBSTRING=$INPUT | cut -d'_' -f 2?

Neil 18.07.2019 21:45

Я бы также добавил '-s' ('--only-delimited'), поскольку без этого флага SUBSTRING будет включать всю строку, если в имени файла нет разделителя. Так было бы безопаснее.

Jean Spector 19.04.2020 13:29

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

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:]]')

Что, если я хочу извлечь цифру / слово из последней строки файла.

A Sahra 16.03.2017 09:27

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

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

Это также не обязательно в свете оператора =~, поддерживаемого [[.

chepner 04.06.2019 20:33

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

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')

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

Aleksandr Levchuk 29.08.2011 09:51

Привет, а почему бы не [[:digit:]]* вместо [^_]*?

YoavKlein 30.09.2020 14:20

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

b=${a:12:5}

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

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

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

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

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

Вы можете делать оба расширения одновременно: ${${a#*_}%_*}. Я использовал это раньше, чтобы объединить несколько операций строки bash, чтобы получить определенный раздел подстроки.

Spencer Rathbun 26.06.2013 16:21

@SpencerRathbun bash: ${${a#*_}%_*}: bad substitution на моем GNU bash 4.2.45.

JB. 28.06.2013 15:02

то же самое здесь с bash 4.1.10 (4): t = "someletters_12345_moreleters.ext"; echo $ {$ {t # * }%} $ {$ {t #}% *}: плохая замена @SpencerRathbun: я никогда не слышал о способе сделать это с одним параметром подстановка, вы можете сказать нам, где у вас это работает?

johnnyB 29.10.2013 20:09

@jonnyB, когда-то раньше это работало. Мои коллеги сказали мне, что он остановился, и они изменили его на команду sed или что-то в этом роде. Глядя на это в истории, я запускал его в сценарии sh, который, вероятно, был тире. На данный момент я не могу заставить его работать.

Spencer Rathbun 29.10.2013 21:52

JB, вы должны пояснить, что «12» - это смещение (отсчитываемое от нуля), а «5» - это длина. Кроме того, +1 за ссылку @gontard, в которой все изложено!

Doktor J 12.09.2014 21:32

Запустив это внутри сценария как "sh run.sh", можно получить ошибку "Плохая подстановка". Чтобы этого избежать, измените права доступа для run.sh (chmod + x run.sh), а затем запустите сценарий как "./run.sh"

Ankur 06.01.2015 13:13

@Ankur: то, что вы пишете, в основном ¹ правильно, но совершенно не по теме. Метод вызова определяет, будет ли оболочка работать с использованием семантики POSIX или bash, но этот вопрос помечен как трепать, поэтому предполагается семантика bash. Это тег FAQ # 2, см. Также этот вопрос. [1: «в основном», потому что вы все равно получите плохую замену, если ваш сценарий работает под управлением #! /bin/sh shebang]

JB. 07.01.2015 12:32

предоставление только первого числа ведет себя как substr или «от позиции x до конца»

Sergio Abreu 08.12.2016 13:32

@Picrochole, это чистый ответ на bash. sed явно не чистый bash. Не могли бы вы переместить свой комментарий к тому ответу, который действительно может быть уместным? Или просто сделайте реальный ответ, если вы думаете, что он чего-то стоит.

JB. 21.08.2018 23:46

Работает только с формой ${a:12:5}. Не работает с ${"some_string":12:5} или ${$(basename $my_var):12:5}. ${a:12:5} - это операция над $a, и имеет смысл, что $"some_string" и $$(basename $my_var) недействительны.

Roger Dueck 07.12.2018 23:19

Параметр длины может быть отрицательным: ${a:12:-5} обрезает 12 символов с начала и 5 символов с конца строки.

Mike Shiyan 28.12.2019 19:22

Параметр смещения тоже может быть отрицательным, кстати. Вам просто нужно позаботиться о том, чтобы не приклеить его к двоеточию, иначе bash интерпретирует его как замену :- «Использовать значения по умолчанию». Таким образом, ${a: -12:5} дает 5 символов 12 символов с конца, а ${a: -12:-5} 7 символов между end-12 и end-5.

JB. 30.12.2019 20:21

Есть ли для этого документация?

Maskim 24.01.2020 12:25

@ Maxime.D это все на странице руководства bash. «Расширение параметров»

JB. 24.01.2020 13:06

Встроенное расширение @ SpencerRathbun работает в zsh, но не в bash. Насчет других снарядов не знаю.

Bruce 31.03.2021 14:10

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

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

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

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

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_, будет четыре фальстарта прежде, чем будет найдено совпадение.

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

zebediah49 06.02.2013 03:14

Это действительно самый общий ответ, и его следует принять. Он работает для регулярного выражения, а не только для строки символов в фиксированной позиции или между одним и тем же разделителем (что позволяет использовать cut). Он также не зависит от выполнения внешней команды.

Dan Dascalescu 08.05.2019 21:22

Это здорово! Я адаптировал это для использования разных дилиметров запуска / остановки (замените _) и чисел переменной длины (. Для {5}) для моей ситуации. Может ли кто-нибудь разрушить эту черную магию и объяснить ее?

Paul 15.05.2020 20:37

@Paul Я добавил подробностей к своему ответу. Надеюсь, это поможет.

nicerobot 16.05.2020 22:43

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

Есть что-то вроде startIndex-lastIndex - 1?

Niklas 30.07.2015 11:00

@Niklas В баше, пролы startIndx-$((lastIndx-1))

brown.2179 31.07.2015 16:19
start=5;stop=9; echo "the rain in spain" | cut -c $start-$(($stop-1))
brown.2179 31.07.2015 21:14

Проблема в том, что ввод является динамическим, поскольку я также использую канал для его получения, так что это в основном. git log --oneline | head -1 | cut -c 9-(end -1)

Niklas 31.07.2015 21:19

Это можно сделать с помощью cut if разбить на две части как line=git log --oneline | голова -1` && echo $ line | cut -c 9 - $ (($ {# line} -1)) `но в этом конкретном случае может быть лучше использовать sed как git log --oneline | head -1 | sed -e 's/^[a-z0-9]* //g'

brown.2179 03.08.2015 16:50

Эта команда отлично подходит для получения отметок времени и т. д. Из таких команд, как stat! Экономит время!

Sean Halls 17.11.2016 22:19

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

Если кому-то нужна более подробная информация, вы также можете поискать ее в 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.

Очень важное предостережение с отрицательными значениями, как указано выше: Арифметические выражения, начинающиеся с -, должны отделяться пробелом от предыдущего:, чтобы их можно было отличить от раскрытия «Использовать значения по умолчанию». Итак, чтобы получить последние четыре символа переменной: ${var: -4}

sshow 27.07.2017 20:22

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

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

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

это не чистое решение bash, я думаю, оно работает в чистой оболочке (/ bin / sh)

kayn 16.05.2014 19:23

+1 Вы могли бы написать это по-другому, чтобы не сбрасывать IFS и позиционные параметры: IFS=_ read -r _ digs _ <<< "$a"; echo "$digs"

kojiro 16.06.2014 17:33

Это подлежит расширению имени пути! (так что он сломан).

gniourf_gniourf 05.10.2015 11:48

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

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

Это очень специфично для этого ввода. Единственное общее решение общего вопроса (который должен был задать OP) - это использовать регулярное выражение.

Dan Dascalescu 08.05.2019 21:27

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

I have a filename with x number of characters then a five digit sequence surrounded by a single underscore on either side then another set of x number of characters. I want to take the 5 digit number and put that into a variable.

Я нашел несколько полезных для 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).

Я считаю, что в настоящее время нет необходимости использовать egrep, сама команда предупреждает вас: Invocation as 'egrep' is deprecated; use 'grep -E' instead. Я отредактировал ваш ответ.

Neurotransmitter 16.06.2014 17:27

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

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

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

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

Нам нужно использовать регулярные выражения (regex) .
Чтобы выбрать только первый запуск (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";'

Но мы могли бы сделать это напрямую в баше(1):

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

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

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

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

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

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345

круто, но требует как минимум bash v4

oHo 25.11.2015 17:50

Решение bash:

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

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

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

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

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

Данный 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

Это очень специфично для этого конкретного входа. Единственное общее решение общего вопроса (который должен был задать OP) - это использовать регулярное выражение.

Dan Dascalescu 08.05.2019 21:28

Мне нравится возможность 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.

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

function 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

shell cut - вывести определенный диапазон символов или заданную часть из строки

# method1) с использованием bash

 str=2020-08-08T07:40:00.000Z
 echo ${str:11:8}

# method2) с использованием cut

 str=2020-08-08T07:40:00.000Z
 cut -c12-19 <<< $str

# method3) при работе с awk

 str=2020-08-08T07:40:00.000Z
 awk '{time=gensub(/.{11}(.{8}).*/,"\\1","g",$1); print time}' <<< $str

Другие вопросы по теме