Витягнути підрядку в Bash


728

Давши ім’я файлу у формі someletters_12345_moreleters.ext, я хочу витягнути 5 цифр і помістити їх у змінну.

Отже, щоб підкреслити суть, у мене є ім'я файлу з x числом символів, потім п'ятизначна послідовність, оточена одиничним підкресленням з обох боків, а потім іншим набором символів x. Я хочу взяти п'ятизначне число і помістити його в змінну.

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


5
Відповідь JB чітко виграє голоси - час змінити прийняту відповідь?
Джефф

3
Більшість відповідей, здається, не відповідають на ваше запитання, оскільки це питання неоднозначне. "У мене є ім'я файлу з x числом символів, потім п'ятизначна послідовність, оточена одинарним підкресленням з обох сторін, а потім іншим набором x кількості символів" . За цим визначенням abc_12345_def_67890_ghi_defє коректним вкладом. Що ти хочеш статися? Припустимо, існує лише одна 5-цифрова послідовність. Ви все ще маєте abc_def_12345_ghi_jklабо 1234567_12345_1234567вважаєте, що 12345d_12345_12345eє дійсним, на основі вашого введення, і більшість відповідей нижче не справляться з цим.
gman

2
Це запитання має надто конкретний приклад. Через це він отримав багато конкретних відповідей для цього конкретного випадку (лише цифри, той самий _роздільник, введення, що містить цільовий рядок лише один раз тощо). Кращий (самий загальний і найшвидший) відповідь має, після 10 років, тільки 7 upvotes, в той час як інші обмежені відповіді сотні. Змушує мене втрачати віру в розробників 😞
Дан Даскалеску

Відповіді:


691

Використовуйте розріз :

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

Більш загальні:

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

1
більш загальна відповідь - це саме те, що я шукав, дякую
Берек Брайан

71
Прапор -f приймає індекси на основі 1, а не 0-індекси, якими користувався програміст.
Метью Г

2
INPUT = someletters_12345_moreleters.ext SUBSTRING = $ (ехо $ INPUT | вирізати -d'_ '-f 2) echo $ SUBSTRING
mani deepak

3
Ви повинні правильно використовувати подвійні лапки навколо аргументів, echoякщо тільки ви точно не знаєте, що змінні не можуть містити нерегулярні пробіли або метахарактори оболонки. Дізнатися більше stackoverflow.com/questions/10067266 / ...
tripleee

Число "2" після "-f" означає оболонці для вилучення 2-го набору підрядків.
Сандун

1085

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

b=${a:12:5}

де 12 - зміщення (на основі нуля) і 5 - довжина

Якщо підкреслення навколо цифр є єдиними на вводі, ви можете зняти префікс і суфікс (відповідно) у два етапи:

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

Якщо є інші підкреслення, це, мабуть, можливо в будь-якому випадку, хоч і більш хитро. Якщо хтось знає, як виконати обидва розширення в одному виразі, я також хотів би знати.

Обидва представлені рішення - чистий баш, без нерестування процесів, отже, дуже швидкого.


18
@SpencerRathbun bash: ${${a#*_}%_*}: bad substitutionна моєму базі GNU 4.2.45.
JB.

2
@jonnyB, Деякий час у минулому працював. Як кажуть мої колеги, це зупинилося, і вони змінили його на команду sed чи щось таке. Дивлячись на це в історії, я запускав це за shсценарієм, який, певно, був тире. На даний момент я більше не можу змусити його працювати.
Спенсер Ратбун

22
JB, вам слід уточнити, що "12" - це зміщення (на основі нуля), а "5" - довжина. Крім того, +1 для посилання на @gontard, у якому все викладено!
Doktor J

1
Під час виконання цього сценарію як "sh run.sh", може виникнути помилка заміну. Щоб уникнути цього, змініть дозволи на run.sh (chmod + x run.sh), а потім запустіть сценарій як "./run.sh"
Ankur

2
Параметр зміщення також може бути негативним, BTW. Вам просто потрібно подбати про те, щоб не склеїти його на товстій кишці, інакше 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


2
Чи є щось на кшталт startIndex-lastIndex - 1?
Ніклас

1
@Niklas In bash, proly startIndx-$((lastIndx-1))
brown.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)
Ніклас

Це можна зробити з розрізанням, якщо розірватися на дві частини як line=git log - oneline | голова -1` && відлуння $ лінія | cut -c 9 - $ (($ {# рядок} -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]

Результат:

$ {параметр: зміщення}
       $ {параметр: offset: length}
              Розширення підрядків. Розширюється на максимум символів
              параметр, починаючи з символу, визначеного зміщенням. Якщо
              Довжина опущена, розширюється на підрядок початку параметра
              ing при символі, визначеному зміщенням. довжина і зміщення -
              арифметичні вирази (див. АРИТМЕТИЧНУ ОЦІНКУ нижче). Якщо
              зміщення оцінюється на число менше нуля, використовується значення
              як зміщення від кінця значення параметра. Арифметика
              вирази, що починаються з -, повинні бути розділені пробілом
              від попереднього: відрізняти від Use Default
              Розширення значень. Якщо довжина оцінюється до числа менше, ніж
              нуль, а параметр не @ і не індексований або асоціативний
              масив, він інтерпретується як зміщення від кінця значення
              параметра, а не кількості символів, і розширення
              sion - це символи між двома зміщеннями. Якщо параметр є
              @, результат - це параметри довжини позиції, що починаються з поза
              набір. Якщо параметр - індексоване ім'я масиву, підписане на @ або
              *, результат - члени довжини масиву, що починається з
              $ {параметр [зсув]}. Від'ємне зміщення приймається відносно
              на один більший, ніж максимальний індекс зазначеного масиву. Sub–
              розширення рядка, застосоване до асоціативного масиву, створює не
              оштрафовані результати. Зауважте, що від'ємне зміщення необхідно відокремити
              від товстої кишки хоча б одним пробілом, щоб не заплутатися
              з: - розширенням. Індексація підрядків не базується на нулях, якщо тільки не
              використовуються позиційні параметри, в цьому випадку - індексація
              починається з 1 за замовчуванням. Якщо зміщення дорівнює 0, а позиційне
              параметри використовуються, $ 0 вказується до списку.

2
Дуже важливий застереження з негативними значеннями, як зазначено вище: Арифметичні вирази, що починаються з -, повинні бути відокремлені пробілом від попереднього: відрізнятись від розширення Використовувати значення за замовчуванням. Отже, щоб отримати останні чотири символи ${var: -4}
вару

26

Ось як я це зробив:

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

Пояснення:

Конкретний баш:

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

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

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


3
Це загальний спосіб, який працює, навіть якщо вам потрібно витягти більше ніж одне, як я.
zebediah49

3
Це справді найзагальніша відповідь, і її слід прийняти. Він працює для регулярного вираження, а не просто рядка символів у фіксованому положенні або між тим самим роздільником (що дозволяє cut). Він також не покладається на виконання зовнішньої команди.
Дан Даскалеску

1
Ця відповідь кримінально недоцільна.
чепнер

Це чудово! Я адаптував це для використання різних діаметрів старт / стоп (замініть _) та номери змінної довжини (для {5}) для моєї ситуації. Чи може хтось зламати цю чорну магію і пояснити її?
Павло

1
@Paul Я додав більше деталей у свою відповідь. Сподіваюся, що це допомагає.
nicerobot

21

Я здивований, що такого чистого рішення башу не придумали:

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

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


1
це не чистий баш-розчин, я думаю, він працює в чистому корпусі (/ bin / sh)
kayn

5
+1 Ви можете написати це ще одним способом, щоб уникнути необхідності IFSIFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
скидання

2
Це підлягає розширенню імені! (так зламано).
gniourf_gniourf

20

Спираючись на відповідь jor (що не працює для мене):

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

12
Регулярні вирази - це справжня справа, коли у вас є щось складне і просто підраховувати підкреслення не буде cutцього.
Олександр Левчук

12

Дотримуючись вимог

У мене є ім'я файлу з x числом символів, потім п'ятизначна послідовність, оточена одинарним підкресленням з обох боків, а потім іншим набором x кількість символів. Я хочу взяти п'ятизначне число і помістити його в змінну.

Я знайшов кілька 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.


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

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 посилання на групу n.1 результату регулярного виразів (група 0 - це весь збіг, група 1 - відповідність в дужках у цьому випадку)
  • p прапор призначений для _p_rinting

Всі втечі \є для того, щоб зробити sedобробку регулярних викидів.


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"

За допомогою мого коду ви можете згадати, що саме ви хочете. Пояснення:

#*Це видалить попередній рядок, включаючи відповідний ключ. Тут згаданий нами ключ - _ %Він видалить наступний рядок, включаючи відповідний ключ. Тут ключ, про який ми згадували, - «більше *»

Зробіть самі експерименти, і вам це стане цікавим.


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

Гаразд, тут йде чисто заміщення параметра з порожнім рядком. Caveat полягає в тому, що я визначив деякі маркери та moreletters як лише символи. Якщо вони буквено-цифрові, це не працюватиме так, як є.

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

2
приголомшливий, але вимагає принаймні bash v4
olibre


1

Також є команда bash вбудована 'expr':

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING

4
exprне є вбудованим.
gniourf_gniourf

1
Це також не потрібно в світлі =~оператора, якого підтримує [[.
чепнер

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

Баш-рішення:

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

Будь ласка.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.