Розділіть рядок за роздільником і отримайте N-й елемент


75

У мене є рядок:

one_two_three_four_five

Мені потрібно зберегти у змінному Aзначенні twoта у Bзначенні змінної fourз вищевказаного рядка

Відповіді:


106

Використовуйте cutз _як роздільник полів і отримати необхідні поля:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

Ви також можете використовувати echoта pipe замість рядка Here:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Приклад:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

Чи є альтернатива? Я використовую ksh (не bsh), і він повертає ksh: синтаксична помилка: `<'несподівано
Алекс,

@Alex Перевір мої зміни.
heemayl

Гарна відповідь, у мене є невелике запитання: що трапиться, якщо ваша змінна "$ s" - це папка шляху. Коли я намагаюся вирізати папку шляху, мені подобається наступне: `$ FILE = my_user / my_folder / [файл] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Чи знаєте ви, що тут відбувається?
Генрі Наварро

1
І якщо ви просто хочете останнє поле, використовуючи лише вбудовані оболонки - без необхідності вказувати його положення або коли ви не знаєте кількість полів:echo "${s##*_}"
Amit Naidu

19

Використовуючи лише конструкції POSIX sh, ви можете використовувати конструкції підстановки параметрів для розбору одного роздільника за один раз. Зауважте, що цей код передбачає необхідну кількість полів, інакше останнє поле повторюється.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Крім того, ви можете використовувати незамінену параметр заміни параметрів з вимкненим розширенням підстановки і IFSвстановити його на знак роздільника (це працює лише в тому випадку, якщо роздільник є одним символом без пробілу або якщо будь-яка послідовність пробілів є роздільником).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Це клобує позиційні параметри. Якщо ви робите це у функції, впливають лише позиційні параметри функції.

Ще один підхід - використовувати readвбудований.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

Використання unset IFSне повертається IFSдо типового. Якщо після цього хтось має OldIFS="$IFS"мати нульове значення всередині OldIFS. Крім того, передбачається, що попереднє значення IFS є типовим, чого дуже можливо (і корисно) не бути. Єдине правильне рішення - зберігання old="$IFS"та відновлення пізніше з IFS = "$ old". Або ... скористайтеся підкольором (...). Або, ще краще, прочитайте мою відповідь.
соронтар

@sorontar unset IFSне відновлює IFSзначення за замовчуванням, але повертає розділення поля до ефекту за замовчуванням. Так, це обмеження, але зазвичай прийнятне на практиці. Проблема з передплатою полягає в тому, що нам потрібно отримати дані з неї. Я дійсно показую рішення, яке не змінює стан в кінці, за допомогою read. (Він працює в оболонках POSIX, але IIRC не в оболонці Bourne, тому що він би запускався в підрозділ readзавдяки документу here.) Використання <<<як у вашому відповіді є варіантом, який працює лише в ksh / bash / zsh.
Жиль

Я не бачу проблем навіть із att або heirloom оболонками щодо нижньої оболонки. Усі випробувані оболонки (включаючи старий бурн) забезпечують правильне значення в основній оболонці.
соронтар

Що станеться, якщо мій шлях чимось схожий user/my_folder/[this_is_my_file]*? Що я отримую, виконуючи ці кроки,[this_is_my_file]*
Генрі Наварро

@HenryNavarro Цей вихід не відповідає жодному з фрагментів коду в моїй відповіді. Жоден з них не робить нічого особливого /.
Жиль

17

Хотіла побачити awkвідповідь, ось ось одна:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

1
І якщо ви хочете останній фрагмент - не потрібно вказувати його положення або коли ви не знаєте кількість полів:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu

8

Найпростіший спосіб (для снарядів з <<<):

 IFS='_' read -r a second a fourth a <<<"$string"

Використання тимчасової змінної $aзамість того, $_що одна оболонка скаржиться.

Повний сценарій:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

Не змінюється IFS, не set -fвиникає проблем з (розширення Pathname) Не змінюються позиційні параметри ("$ @").


Для рішення, що переноситься на всі оболонки (так, всі POSIX включені) без зміни IFS або set -f, використовуйте (трохи складніший) еквівалент heredoc:

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Зрозумійте, що ці рішення (і тут, і doc, і використання <<<видалять усі останні лінії).
І що це розроблено для змінного вмісту "одного вкладиша".
Рішення для мультилайнерів можливі, але потребують складніших конструкцій.


Дуже просте рішення можливе в баш-версії 4.4

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

Для оболонок POSIX немає еквівалента, оскільки багато оболонок POSIX не мають масивів.

Для оболонок, що мають масиви, може бути так само просто:
(перевірено, що працює в attsh, lksh, mksh, ksh та bash)

set -f; IFS=_; arr=($string)

Але з великою кількістю додаткової сантехніки для збереження та скидання змінних та параметрів:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

У zsh масиви починаються з 1 і не розділяють рядок за замовчуванням.
Тому потрібно внести деякі зміни, щоб це працювало в zsh.


рішення, які використовуються read , прості до тих пір, поки ОП не захоче витягти 76-й та 127-й елементи з довгої струни ...
don_crissti

@don_crissti Ну, звичайно, але подібний конструкт: readarrayможна було б простіше використовувати для цієї ситуації.
соронтар

@don_crissti Я також додав рішення масиву для оболонок, у яких є масиви. Для оболонок POSIX добре, що не мають масивів, позиційні параметри до 127 елементів - це не "просте" рішення ні в якому разі.
соронтар

2

За допомогою zshвас можна розділити рядок (on _) на масив:

elements=(${(s:_:)string})

а потім отримати доступ до кожного / будь-якого елемента за допомогою індексу масиву:

print -r ${elements[4]}

Майте на увазі, що в zsh(на відміну від ksh/ bash) індекси масиву починаються з 1 .


Будь ласка, не забудьте додати set -fпопередження до першого рішення. ... зірочки, *можливо?
соронтар

@sorontar - чому ти вважаєш, що мені потрібно set -f? Я не використовую read/ IFS. Спробуйте мої рішення, як- *_*_*небудь або як завгодно ...
don_crissti

Не для zsh, але користувач попросив рішення ksh, тож він може спробувати використовувати його в цій оболонці. Попередження допоможе йому уникнути проблеми.
соронтар

1

Чи дозволений розчин пітона?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four

№ погано поганий answet
Радж Кумар

0

Ще один приклад awk; простіший для розуміння.

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Можна використовувати і зі змінними.
Припустимо:
this_str = "one_two_three_four_five"
Потім працює наступне:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' `
B =` echo $ {this_str} | awk -F_ '{print $ 2}' `
C =` echo $ {this_str} | awk -F_ '{print $ 3}' `
... і так далі ...

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