Определение номера колонки таблицы по содержимому (заголовку), для вывода ее awk

Преамбула

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'

Скрипты целиком

На GitHub
Скачать одним архивом с Mega.nz

Проверка состояния сетевого устройства (например, eth0) в Linux

Преамбула

Бывает необходимо проверить состояние (статус) того или иного сетевого устройства, например, сетевой карты. Вообще вариантов может быть три:
1. Устройство работает (up)
2. Устройство есть, но не работает в данный момент (down)
3. Устройства нет вообще.

Описание принципа действия

Чтобы определить состояние сетевого устройства, нужно проанализировать вывод команды ifconfig. Если устройство вообще есть, то оно будет в выводе ifconfig -a (ключ -a — все устройства). Если устройство есть, но в данный момент не работает, в выводе ifconfig -a оно будет, в выводе ifconfig — нет.

Например, устройство veth1 в данный момент не работает, вывод ifconfig:

chaosadm@chaos:~# ifconfig
lo: flags=73  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10
        loop  txqueuelen 1  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Вывод ifconfig -a для сравнения:

chaosadm@chaos:~# ifconfig -a
lo: flags=73  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10
        loop  txqueuelen 1  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth1: flags=4098  mtu 1500
        inet 10.10.0.119  netmask 255.255.0.0  broadcast 10.10.0.255
        ether 62:4e:2c:ad:06:fa  txqueuelen 1000  (Ethernet)
        RX packets 1034  bytes 93233 (91.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 136  bytes 12047 (11.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


Значит достаточно отфильтровать вывод ifconfig и ifconfig -a grep‘ом по имени конкретного сетевого устройства, и заставить grep посчитать количество строк (ключ -c):

ifconfig -a | grep eth0 -c

eth0 — имя устройства.

Если количество строк будет 0 — устройства совсем нет.

ifconfig | grep $DEV -c

Если количество строк будет больше 0 — устройство работает (up), если предыдущая команда выдала 1, а эта команда нет, то устройство есть, но не работает (down). Если используются псевдонимы, то надо использовать полные имена (например, eth0:1).

Скрипт

Все это дело можно автоматизировать скриптом.
Продумаем коды возврата: 0 — сетевое устройство работает (up), 1 — устройство не работает (down), 2 — устройство не найдено (none), 3 — неправильные параметры скрипта или запрос справки.
Добавим в скрипт дополнительный параметр -s — если он указан, скрипт не будет выводить сообщений на консоль, а только сигнализировать о статусе сетевого устройства кодом возврата (для использования в других скриптах).

1. Проверяем правильность параметров, выводим помощь:

#!/bin/bash

#check network device status
#exit codes 0 - device up 1 - device down 2 - none device 
#3 - help or wrong parameters

SLNT=0

print_help()
{
    echo "Use "`basename $0`"  [-s]"
    echo " - network device name, e.g. eth0"
    echo "-s - silent mode, no console output"
}

#parameters check and set silent mode
if [ $# -eq 0 ]; then
    echo "Wrong parameters!"
    echo
    print_help
    exit 3
else
    if [ $# -eq 2 ]; then
	if [[ "$2" == "-s" ]]; then
	    SLNT=1
	else
	    echo "Wrong parameters!"
	    echo
	    print_help
	    exit 3
	fi
    fi
fi

#print help
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
    print_help
    exit 3
fi

2. Проверяем наличие устройства вообще:

DEV=$1

# check device exist
DOWN=`ifconfig -a | grep $DEV -c`
if [ $DOWN -eq 0 ]; then
    if [ $SLNT -eq 0 ];then
	echo "Device $DEV: NONE"
    fi
    exit 2
fi

3. Проверяем, работает оно или нет:

#check up/down status
UP=`ifconfig | grep $DEV -c`
if [ $UP -eq 0 ]; then #device down
    if [ $SLNT -eq 0 ];then
	echo "Device $DEV: DOWN"
    fi
    exit 1
else
    if [ $SLNT -eq 0 ];then
	echo "Device $DEV: UP"
    fi
    exit 0
fi

Скрипт целиком

На PasteBin
На GitHub
Пример использования в другом скрипте, который организует Network Namespace’ы