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


Используйте резать:
echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2
Более общий:
INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
более общий ответ - это именно то, что я искал, спасибо
Флаг -f принимает индексы, начинающиеся с 1, а не индексы с отсчетом от 0, к которым привык бы программист.
INPUT = someletters_12345_moreleters.ext SUBSTRING = $ (echo $ INPUT | cut -d'_ '-f 2) echo $ SUBSTRING
Вы должны правильно использовать двойные кавычки вокруг аргументов echo, если вы точно не знаете, что переменные не могут содержать нерегулярные пробелы или метасимволы оболочки. См. Далее stackoverflow.com/questions/10067266/…
Число «2» после «-f» указывает оболочке извлечь второй набор подстроки.
Может это глупый вопрос, но почему не работает SUBSTRING=$INPUT | cut -d'_' -f 2?
Я бы также добавил '-s' ('--only-delimited'), поскольку без этого флага SUBSTRING будет включать всю строку, если в имени файла нет разделителя. Так было бы безопаснее.
Общее решение, в котором число может быть в любом месте имени файла, используя первую из таких последовательностей:
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:]]')
Что, если я хочу извлечь цифру / слово из последней строки файла.
Также есть встроенная команда bash 'expr':
INPUT = "someletters_12345_moreleters.ext"
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `
echo $SUBSTRING
expr не является встроенным.
Это также не обязательно в свете оператора =~, поддерживаемого [[.
Основываясь на ответе Джора (который не работает для меня):
substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
Регулярные выражения - это реальная проблема, когда у вас есть что-то сложное, и простой подсчет подчеркиваний не поможет.
Привет, а почему бы не [[:digit:]]* вместо [^_]*?
Если Икс является константой, следующее раскрытие параметра выполняет извлечение подстроки:
b=${a:12:5}
где 12 - это смещение (отсчитываемое от нуля), а 5 - длина
Если символы подчеркивания вокруг цифр - единственные во входных данных, вы можете удалить префикс и суффикс (соответственно) в два этапа:
tmp=${a#*_} # remove prefix ending in "_"
b=${tmp%_*} # remove suffix starting with "_"
Если есть другие символы подчеркивания, это, вероятно, в любом случае возможно, хотя и более сложное. Если кто-нибудь знает, как выполнить оба раскрытия в одном выражении, я тоже хотел бы знать.
Оба представленных решения представляют собой чистый bash, без необходимости создания процессов, поэтому они очень быстрые.
Вы можете делать оба расширения одновременно: ${${a#*_}%_*}. Я использовал это раньше, чтобы объединить несколько операций строки bash, чтобы получить определенный раздел подстроки.
@SpencerRathbun bash: ${${a#*_}%_*}: bad substitution на моем GNU bash 4.2.45.
то же самое здесь с bash 4.1.10 (4): t = "someletters_12345_moreleters.ext"; echo $ {$ {t # * }%} $ {$ {t #}% *}: плохая замена @SpencerRathbun: я никогда не слышал о способе сделать это с одним параметром подстановка, вы можете сказать нам, где у вас это работает?
@jonnyB, когда-то раньше это работало. Мои коллеги сказали мне, что он остановился, и они изменили его на команду sed или что-то в этом роде. Глядя на это в истории, я запускал его в сценарии sh, который, вероятно, был тире. На данный момент я не могу заставить его работать.
JB, вы должны пояснить, что «12» - это смещение (отсчитываемое от нуля), а «5» - это длина. Кроме того, +1 за ссылку @gontard, в которой все изложено!
Запустив это внутри сценария как "sh run.sh", можно получить ошибку "Плохая подстановка". Чтобы этого избежать, измените права доступа для run.sh (chmod + x run.sh), а затем запустите сценарий как "./run.sh"
@Ankur: то, что вы пишете, в основном ¹ правильно, но совершенно не по теме. Метод вызова определяет, будет ли оболочка работать с использованием семантики POSIX или bash, но этот вопрос помечен как трепать, поэтому предполагается семантика bash. Это тег FAQ # 2, см. Также этот вопрос. [1: «в основном», потому что вы все равно получите плохую замену, если ваш сценарий работает под управлением #! /bin/sh shebang]
предоставление только первого числа ведет себя как substr или «от позиции x до конца»
@Picrochole, это чистый ответ на bash. sed явно не чистый bash. Не могли бы вы переместить свой комментарий к тому ответу, который действительно может быть уместным? Или просто сделайте реальный ответ, если вы думаете, что он чего-то стоит.
Работает только с формой ${a:12:5}. Не работает с ${"some_string":12:5} или ${$(basename $my_var):12:5}. ${a:12:5} - это операция над $a, и имеет смысл, что $"some_string" и $$(basename $my_var) недействительны.
Параметр длины может быть отрицательным: ${a:12:-5} обрезает 12 символов с начала и 5 символов с конца строки.
Параметр смещения тоже может быть отрицательным, кстати. Вам просто нужно позаботиться о том, чтобы не приклеить его к двоеточию, иначе bash интерпретирует его как замену :- «Использовать значения по умолчанию». Таким образом, ${a: -12:5} дает 5 символов 12 символов с конца, а ${a: -12:-5} 7 символов между end-12 и end-5.
Есть ли для этого документация?
@ Maxime.D это все на странице руководства bash. «Расширение параметров»
Встроенное расширение @ SpencerRathbun работает в zsh, но не в bash. Насчет других снарядов не знаю.
Без каких-либо подпроцессов вы можете:
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_, будет четыре фальстарта прежде, чем будет найдено совпадение.
Это общий способ, который работает, даже если вам нужно извлечь более одного объекта, как это сделал я.
Это действительно самый общий ответ, и его следует принять. Он работает для регулярного выражения, а не только для строки символов в фиксированной позиции или между одним и тем же разделителем (что позволяет использовать cut). Он также не зависит от выполнения внешней команды.
Это здорово! Я адаптировал это для использования разных дилиметров запуска / остановки (замените _) и чисел переменной длины (. Для {5}) для моей ситуации. Может ли кто-нибудь разрушить эту черную магию и объяснить ее?
@Paul Я добавил подробностей к своему ответу. Надеюсь, это поможет.
просто попробуйте использовать cut -c startIndx-stopIndx
Есть что-то вроде startIndex-lastIndex - 1?
@Niklas В баше, пролы startIndx-$((lastIndx-1))
start=5;stop=9; echo "the rain in spain" | cut -c $start-$(($stop-1))Проблема в том, что ввод является динамическим, поскольку я также использую канал для его получения, так что это в основном. git log --oneline | head -1 | cut -c 9-(end -1)
Это можно сделать с помощью 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'
Эта команда отлично подходит для получения отметок времени и т. д. Из таких команд, как stat! Экономит время!
Вот решение с префиксом-суффиксом (аналогично решениям, данным 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}
Я удивлен, что это чистое решение bash не появилось:
a = "someletters_12345_moreleters.ext"
IFS = "_"
set $a
echo $2
# prints 12345
Вероятно, вы захотите сбросить IFS до того значения, которое было раньше, или unset IFS после этого!
это не чистое решение bash, я думаю, оно работает в чистой оболочке (/ bin / sh)
+1 Вы могли бы написать это по-другому, чтобы не сбрасывать IFS и позиционные параметры: IFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
Это подлежит расширению имени пути! (так что он сломан).
аналогично substr ('abcdefg', 2-1, 3) в php:
echo 'abcdefg'|tail -c +2|head -c 3
Это очень специфично для этого ввода. Единственное общее решение общего вопроса (который должен был задать OP) - это использовать регулярное выражение.
Следуя требованиям
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. Я отредактировал ваш ответ.
Немного поздно, но я столкнулся с этой проблемой и обнаружил следующее:
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
Решение 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) - это использовать регулярное выражение.
Мне нравится возможность 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
Похоже, что большинство ответов не отвечают на ваш вопрос, потому что вопрос неоднозначен. «У меня есть имя файла с количеством символов x, затем пятизначная последовательность, окруженная одним подчеркиванием с обеих сторон, затем еще один набор с количеством символов x». По этому определению
abc_12345_def_67890_ghi_defявляется допустимым входом. Чего ты хочешь? Предположим, есть только одна последовательность из 5 цифр. У вас все еще естьabc_def_12345_ghi_jkl,1234567_12345_1234567или12345d_12345_12345eв качестве допустимого ввода на основе вашего определения ввода, и большинство ответов ниже не справятся с этим.