Разбейте строку по разделителю и получите N-й элемент

77

У меня есть строка:

one_two_three_four_five

Мне нужно сохранить Aзначение twoпеременной и Bзначение переменной fourиз приведенной выше строки

Alex
источник

Ответы:

109

Используйте cutс _в качестве разделителя полей и получите нужные поля:

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

Вы также можете использовать echoи конвейер вместо строки 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
heemayl
источник
Есть ли альтернатива? Я использую ksh (не bsh), и он возвращает синтаксическую ошибку ksh: `<'неожиданно
Alex
@ Алекс Проверить мои правки.
Heemayl
Хороший ответ, у меня есть маленький вопрос: что произойдет, если ваша переменная "$ s" является папкой пути. Когда я пытаюсь вырезать папку пути, я делаю следующее: `$ FILE = my_user / my_folder / [file] *` $ 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в подоболочке из-за нижеследующего документа.) Использование <<<ответа as in you - это вариант, который работает только в ksh / bash / zsh.
Жиль "ТАК - перестань быть злым"
Я не вижу проблемы даже с att или семейной оболочкой о подоболочке. Все протестированные оболочки (включая старую борну) дают правильное значение в основной оболочке.
Соронтар
Что произойдет, если мой путь будет что-то вроде 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'
Амит Найду
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(расширение имени пути). Без изменений позиционных параметров ("$ @").


Для решения, переносимого на все оболочки (да, включая все 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"

Поймите, что это решение (как здесь, так и при использовании <<<, удалит все завершающие символы новой строки.
И что оно предназначено для переменного содержимого "в один слой".
Решения для мультилиний могут быть, но требуют более сложных конструкций.


В bash версии 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.

sorontar
источник
Решения, которые используют read , просты, если OP не хочет извлекать 76-й и 127-й элементы из длинной строки ...
don_crissti
@don_crissti Ну да, конечно, но похожая конструкция: readarrayможет быть проще использовать в такой ситуации.
соронтар
@don_crissti Я также добавил решение для массивов для оболочек, которые имеют массивы. Что касается оболочек POSIX, ну, не имея массивов, позиционные параметры до 127 элементов - это не «простое» решение ни в какой мере.
соронтар
2

С помощью zshвы можете разбить строку (в _) на массив:

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

а затем получить доступ к каждому / любому элементу через индекс массива:

print -r ${elements[4]}

Имейте в виду, что в zsh(в отличие от ksh/ bash) индексы массива начинаются с 1 .

оборота дон_криссти
источник
Пожалуйста, не забудьте добавить set -fпредупреждение к первому решению. ... звездочки *может быть?
соронтар
@sorontar - почему вы думаете, что мне нужно set -f? Я не использую read/ IFS. Попробуйте мои решения со строкой, как *_*_*или что-то еще ...
don_crissti
Не для zsh, но пользователь запросил решение ksh, поэтому он может попытаться использовать его в этой оболочке. Предупреждение поможет ему избежать проблемы.
соронтар
1

Разрешено ли решение на Python?

# 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
fhgd
источник
Плохой плохой ответ
Радж Кумар
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}' `
... и так далее ...

user274900
источник