awk
, удобный инструмент для работы с табличными данными (текстовыми файлами с разделителями), однако, бывают ситуации, когда необходимо определить нужную колонку в таблице не по номеру, а по содержимому.
Например, имеется такая таблица (в виде текстового файла с разделителями табуляцией), скажем, абстрактная зарплатная ведомость:
Задача — вывести колонку с номерами карт (CardID). В простейшем случае, читаем файл с помощью cat
, вывод передаем awk
и выводим на экран колонку 3.
cat salary1.txt|awk '{print $3}'
Результат:
CardID
6666-6666-6666-0001
6666-6666-6666-0002
6666-6666-6666-0003
6666-6666-6666-0004
6666-6666-6666-0005
6666-6666-6666-0006
6666-6666-6666-0007
6666-6666-6666-0008
6666-6666-6666-0009
6666-6666-6666-0010
6666-6666-6666-0011
6666-6666-6666-0012
6666-6666-6666-0013
Сложнее будет, если место колонки в таблице поменяется, например, в таблицу добавили информацию о банках и воинских званиях сотрудников:
Понятно, что можно номер колонки поменять, или задавать в параметре скрипта, но лучше найти колонку по заголовку. Недавно как раз попалась вполне производственная задача, где программа, в зависимости от ОС, выводила то одну, то другую табличку.
Нашел вопрос по этому поводу на toster.ru, заданный примерно год назад, но без ответов, что удивительно. А ведь там такие акулы и киты программирования с Хабра плавают…
Впрочем, решение тоже нашлось, хотя на «чистом awk
» получались какие-то громоздкие конструкции, так что проще, на мой взгляд, было решить с помощью других утилит.
1. Вытаскиваем строчку с заголовками grep
‘ом:
cat salary2.txt|grep -w "CardID"
параметр -w
, указывает grep
‘у вытащить только строки, содержащие слово целиком.
Вывод:
Family Name MilRank BankID CardID Sum
2. Заменяем символы табуляции (\t
) на символы перевода строк (\n
):
cat salary2.txt|grep -w "CardID"|sed 's/\t/\n/g'
Вывод:
Family
Name
MilRank
BankID
CardID
Sum
Далее, удобства для, буду приводить только следующую команду, без предыдущих.
3. Нумеруем строки с помощью утилиты nl
nl
Вывод:
1 Family 2 Name 3 MilRank 4 BankID 5 CardID 6 Sum
4. grep
‘ом вытаскиваем строку с номером нужной колонки:
grep -w "CardID"
Вывод:
5 CardID
5. Получаем номер колонки с помощью awk
:
awk '{print $1}'
Вывод:
5
Пункты 3 и 4 можно оптимизировать, заставив grep
нумеровать строки с помощью ключа -n
:
grep -w -n "CardID"
Вывод:
5:CardID
Тогда для окончательного определения номера, нужно будет задать awk
разделитель двоеточие:
awk -F":" '{print $1}'
Вывод:
5
6. Помещаем результат работы всей команды целиком в переменную:
COLNUM=`cat salary2.txt|grep -w "CardID"|sed 's/\t/\n/g'|nl|grep -w "CardID"|awk '{print $1}'`
или
COLNUM=`cat salary2.txt|grep -w "CardID"|sed 's/\t/\n/g'|grep -w -n "CardID"|awk -F":" '{print $1}'`
7. Теперь в awk
нужно передать значение переменной из bash скрипта:
Делается это с помощью ключа -v имя=значение
имя
— имя переменной, которое будет использовано внутри скрипта awk
значение
— значение переменной
cat salary2.txt|awk -v cnum="${COLNUM}" '{print $cnum}'
Вывод:
CardID
6666-6666-6666-0001
6666-6666-6666-0002
6666-6666-6666-0003
6666-6666-6666-0004
6666-6666-6666-0005
6666-6666-6666-0006
6666-6666-6666-0007
6666-6666-6666-0008
6666-6666-6666-0009
6666-6666-6666-0010
6666-6666-6666-0011
6666-6666-6666-0012
6666-6666-6666-0013
Чтобы не выводить сам заголовок, можно добавить sed '1d'
:
cat salary2.txt|sed '1d'|awk -v cnum="${COLNUM}" '{print $cnum}'
Предположим, что в файле две таблицы:
Тогда в переменной $COLNUM
после выполнения первой команды, окажется значение
5 11
, что на самом деле не есть хорошо. Думаю, понятно, из-за чего этот эффект происходит. Нужно добавить команду head -n1
, чтобы оставить только одну строчку после первого grep "CardID"
.
COLNUM=`cat salary3.txt|grep "CardID"|head -n1|sed 's/\t/\n/g'|grep -w -n "CardID"|awk -F":" '{print $1}'`
В следующей команде так же желательно установить awk
разделитель «только символ табуляции», чтоб не отреагировал на пятое слово не в таблице (по умолчанию у awk
разделитель полей — табуляция и пробел).
cat salary3.txt|awk -v cnum="${COLNUM}" -F "\t" '{print $cnum}'
Ну и можно удалить sed
‘ом пустые строки и строки, содержащие заголовок CardID:
cat salary3.txt|awk -v cnum="${COLNUM}" -F "\t" '{print $cnum}'|sed '/^$/d'|sed '/CardID/d'