Как разбить строку с разделителями в массив в awk?

169

Как разбить строку , если она содержит символы трубы |в нем. Я хочу разделить их, чтобы быть в массиве.

Я попытался

echo "12:23:11" | awk '{split($0,a,":"); print a[3] a[2] a[1]}'

Который работает отлично. Если моя строка похожа, "12|23|11"то как мне разбить их на массив?

Мохамед Сали
источник
3
Обратите внимание, что ваш вывод объединяет элементы массива без разделителя. Если вы хотите, чтобы они разделялись OFS, вставьте между ними запятые, чтобы они printрассматривались как отдельные аргументы.
dubiousjim
Или вы можете использовать sed:echo "12:23:11" | sed "s/.*://"
слякотный
@slushy: ваша команда совсем не то, что нужно спрашивающему. Ваша команда ( echo "12:23:11" | sed "s/.*://") удаляет все до (и включая) последнего «:», оставляя только «11» ... она работает, чтобы получить последнее число, но должна быть изменена (трудно читаемым способом), чтобы получить 2-й номер и т. д. awk (и разделение awk) гораздо более элегантно и читабельно.
Оливье Дюлак
если вам нужно разделить на одного персонажа, которого вы можете использоватьcut
ccpizza

Ответы:

274

Ты пробовала:

echo "12|23|11" | awk '{split($0,a,"|"); print a[3],a[2],a[1]}'
Калин Пол Александру
источник
2
@Mohamed Saligh, если вы находитесь в Solaris, вам нужно использовать / usr / xpg4 / bin / awk , учитывая длину строки.
Дмитрий Радулов
5
«не работает для меня». особенно с двоеточиями между отображаемыми значениями и разбиением, настроенными для разделения на '|' ??? Опечатка? Всем удачи.
Shellter
1
Лучше с некоторым синтаксическим объяснением.
Олстон
2
Это не будет работать в GNU awk, потому что третий аргумент to split- это регулярное выражение и |специальный символ, который необходимо экранировать. Использованиеsplit($0, a, "\|")
WhiteWind
1
@WhiteWind: еще один способ «убедиться» в том, что |он рассматривается как символ, а не как специальный символ, - поместить его между []: т.е. split($0, a, "[|]") # мне нравится это лучше, чем '\ |', в некоторых случаях, особенно как вариант regexp ( Perl против grep против .. других?) может иметь "|" в буквальном смысле и "\ |" рассматривается как разделитель регулярных выражений, а не наоборот ... ymmv
Оливье Дюлак
119

Чтобы разбить строку на массив, awkмы используем функцию split():

 awk '{split($0, a, ":")}'
 #           ^^  ^  ^^^
 #            |  |   |
 #       string  |   delimiter
 #               |
 #               array to store the pieces

Если разделитель не указан, используется значение по FSумолчанию, равное пробелу:

$ awk '{split($0, a); print a[2]}' <<< "a:b c:d e"
c:d

Мы можем дать разделитель, например ::

$ awk '{split($0, a, ":"); print a[2]}' <<< "a:b c:d e"
b c

Что эквивалентно установке через FS:

$ awk -F: '{split($0, a); print a[1]}' <<< "a:b c:d e"
b c

В gawk вы также можете указать разделитель как регулярное выражение:

$ awk '{split($0, a, ":*"); print a[2]}' <<< "a:::b c::d e" #note multiple :
b c

И даже посмотрите, какой разделитель был на каждом шаге, используя его четвертый параметр:

$ awk '{split($0, a, ":*", sep); print a[2]; print sep[1]}' <<< "a:::b c::d e"
b c
:::

Давайте процитируем man-страницу GNU awk :

split (строка, массив [, fieldsep [, seps]])

Разделите строку на части, разделенные fieldsep, и сохраните части в массиве и строки-разделители в массиве seps . Первый кусок хранится в array[1], второй кусок в array[2]и так далее. Строковое значение третьего аргумента, fieldsep , является регулярным выражением, описывающим, где разбивать строку (так же, как FS может быть регулярным выражением, описывающим, где разбивать входные записи). Если fieldsep опущен, используется значение FS . split()возвращает количество созданных элементов. seps - это gawkрасширение с seps[i]разделительной строкой междуarray[i]и array[i+1]. Если fieldsep является одним пробелом, то любой начальный пробел входит в seps[0]и любой конечный пробел входит в seps[n], где n - возвращаемое значение split()(то есть, количество элементов в массиве).

Федорки "ТАК прекратить вредить"
источник
просто упомяните, что вы используете gnu awk, а не обычный awk (который не хранит разделители в seps [] и имеет другие ограничения)
Olivier Dulac
17

Пожалуйста, будьте более конкретны! Что вы подразумеваете под "это не работает"? Отправьте точный вывод (или сообщение об ошибке), вашу ОС и версию awk:

% awk -F\| '{
  for (i = 0; ++i <= NF;)
    print i, $i
  }' <<<'12|23|11'
1 12
2 23
3 11

Или, используя split:

% awk '{
  n = split($0, t, "|")
  for (i = 0; ++i <= n;)
    print i, t[i]
  }' <<<'12|23|11'
1 12
2 23
3 11

Редактировать: в Solaris вам нужно использовать POSIX awk ( / usr / xpg4 / bin / awk ) для правильной обработки 4000 полей.

Димитр Радулов
источник
for(i = 0или for(i = 1?
PiotrNycz
я = 0, потому что я использую ++ я после (не я ++).
Димитр Радулов
3
Хорошо, я этого не заметил. Я твердо верю, что более читабельным было бы for (i = 1; i <= n; ++i)...
PiotrNycz
5

Мне не нравится echo "..." | awk ...решение, так как оно вызывает ненужные forkи execсистемные вызовы.

Я предпочитаю решение Димитра с небольшим поворотом

awk -F\| '{print $3 $2 $1}' <<<'12|23|11'

Или немного более короткая версия:

awk -F\| '$0=$3 $2 $1' <<<'12|23|11'

В этом случае выходная запись соединяется, что является истинным условием, поэтому она печатается.

В этом конкретном случае stdinперенаправление можно избежать, установив внутренняя переменная:

awk -v T='12|23|11' 'BEGIN{split(T,a,"|");print a[3] a[2] a[1]}'

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

T='12|23|11';echo -n ${T##*|};T=${T%|*};echo ${T#*|}${T%|*}
T='12|23|11';echo ${T:6}${T:3:2}${T:0:2}

Результат во всех случаях

112312
TrueY
источник
Я думаю, что конечным результатом должны были быть ссылки на переменные массива awk, независимо от приведенного примера вывода на печать. Но вы пропустили очень простой случай, чтобы получить конечный результат. T = '12: 23: 11 '; echo $ {T //:}
Даниэль Листон
@DanielListon Вы правы! Спасибо! Я не знал, что трейлинг / можно оставить в этом bashвыражении ...
TrueY
4

На самом деле awkимеет функцию под названием «Поле ввода Сепаратор Variable» ссылка . Вот как это использовать. Это не совсем массив, но он использует внутренние переменные $. Разбить простую строку проще.

echo "12|23|11" | awk 'BEGIN {FS="|";} { print $1, $2, $3 }'
Sven
источник
3
echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'

должно сработать.

codaddict
источник
3
echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'
Schildmeijer
источник
1

Шутка? :)

Как насчет echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'

Это мой вывод:

p2> echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'
112312

так что я думаю, что это работает в конце концов ..

duedl0r
источник
это из-за длины строки? с тех пор моя длина строки 4000. любые идеи
Мохамед Салих
1

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

# Convert to an array
_ITEMS=($(echo "12|23|11" | tr '|' '\n'))

# Output array items
for _ITEM in "${_ITEMS[@]}"; do
  echo "Item: ${_ITEM}"
done

Выход будет:

Item: 12
Item: 23
Item: 11
Qorbani
источник