Записать вывод awk в несколько переменных bash
Или как раскидать результат работы awk по нескольким переменным.
Предположим, у нас есть некоторая таблица в виде файла CSV с набором полей, например таких
Login,FullName,Phone,Room,WorkTime
и разделителем полей ,
(запятая):verb666,Misha Verbitsky,+415314499922,42,11:00-16:00
ktvs421,Vasiliy Kotov,+415314499966,77a,00:00-06:00
dkldn89,Dmitry Kaledin,+415314499949,65b,22:00-00:00
vfurry1,Veniamin Furman,+415314499900,99,12:20-19:25
tpunk56,Tolik Punkoff,+415314499911,59,00:00-11:00
Нужно вытащить из нее некоторые данные, и далее как-либо обработать. Вытащить данные можно с помощью
awk
, используя оператор print
, но возникает вопрос, как передать данные обратно в bash.Предположим, что заголовок удален, в файле остались только данные.
В bash есть встроенная команда
eval
, преобразующая переданную ей строку в команду или набор команд оболочки, и запускающая ее на выполнение. Этим и воспользуемся.1. Организуем цикл, в котором будем производить обработку данных:
IFS_=$IFS
IFS=$'\n'
for TMPSTRING in $(cat "demotable.txt")
do
#тут будет код
done
IFS=$IFS_
Перед циклом я подправил переменную
$IFS
содержащую глобальные разделители, в нее, в частности, "смотрят" операторы циклов, чтобы определить, где начинается следующий элемент. По умолчанию переменная $IFS
содержит пробел, табуляцию и перевод строки, но поскольку у нас есть данные с пробелом, то это не подходит, цикл будет работать неверно. Потому сохраняем старое значение во временную переменную, устанавливаем новое значение в перевод строки (\n
). После цикла возвращаем значение на место.В цикле организуем разбор данных:
echo "$TMPSTRING"|awk -F "," '{print "LOGIN=" $1; print "FULLNAME=" $2
print "PHONE=" $3; print "ROOM=" $4; print "WORKTIME=" $5 }'
Если запустить скрипт сейчас, то он выведет следующее:
LOGIN=verb666
FULLNAME=Misha Verbitsky
PHONE=+415314499922
ROOM=42
WORKTIME=11:00-16:00
Т.е. уже похоже на присваивание значений переменным bash, но есть проблема. Если мы сейчас скормим вывод
awk
eval
'у, то получим ошибку, например такую:./awk2vars01: line 8: Verbitsky: command not found
А если бы и не получили, то в переменных могла бы оказаться всякая ерунда, строки необходимо экранировать кавычками.
awk print
и вывод кавычкиКавычки для оператора
print
awk
являются служебными символами, в двойные кавычки берутся строковые литералы, т.е. те строки, которые нужно вывести без изменений, как например, "LOGIN="
в коде выше, а в одинарные - вся программа awk
. Экранирование (\"
или \'
) в операторе print
приведет к ошибке.Решение - завести внутреннюю переменную
awk
, содержащую кавычку, и печатать ее в нужном месте:echo "$TMPSTRING"|sed -e 's~'\''~~g'|awk -F "," '{print "LOGIN=" sq $1 sq;
print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq; print"ROOM=" sq $4 sq;
print "WORKTIME=" sq $5 sq}' sq="'"
Поскольку данные строки далее будут переданы в
eval
и обработаны как команды оболочки, то необходимо позаботиться о безопасности, и использовать только одинарные кавычки, а также удалять одинарные кавычки из входных строк, при передаче их awk:Об опасности использования eval в bash-скриптах. Копия
Вывод:
LOGIN='verb666'
FULLNAME='Misha Verbitsky'
PHONE='+415314499922'
ROOM='42'
Теперь можно обернуть все это в
eval
, чтобы раскидать результат работы awk
по переменным.eval $(echo "$TMPSTRING"|sed -e 's~'\''~~g'|awk -F "," '{print "LOGIN=" sq $1 sq;
print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq;
print"ROOM=" sq $4 sq;
print "WORKTIME=" sq $5 sq}' sq="'")
В демо-скрипте я просто вывожу данные на консоль, в реальном скрипте, что понятно, можно делать обработку данных в переменных bash.
echo "Login: $LOGIN"
echo "Full name: $FULLNAME"
echo "Phone: $PHONE"
echo "Room: $ROOM"
echo "Work time: $WORKTIME"
Вывод:
Login: verb666
Full name: Misha Verbitsky
Phone: +415314499922
Room: 42
Work time: 11:00-16:00
...
Скрипты полностью можно посмотреть на GitHub
На самом деле циклы в bash работают довольно медленно, и на реальной производственной задаче такой код довольно сильно тормозил, отрабатывая на таблице в 100 записей примерно 1 секунду:
IFS_=$IFS
IFS=$'\n'
J=0
for TMPSTRING in $(cat "data/servers")
do
let "J+=1"
#extract data
eval $(echo "$TMPSTRING"|sed -e 's~'\''~~g'|awk -F "," '{print "HOST_NAME="dq $1 dq;
print "IP="dq $2 dq;print "SCORE=" dq $3 dq;print "PING=" dq $4 dq;
print "SPEED=" dq $5 dq;print "COUNTRY=" dq $6 dq;
print "COUNTRYSHORT=" dq $7 dq; print "NUMVPNSESSION=" dq $8 dq;
print "UPTIME=" dq $9 dq;print "TOTALUSERS=" dq $10 dq;
print "TOTALTRAFFIC=" dq $11 dq;print "LOGTYPE=" dq $12 dq;
print "OPERATOR=" dq $13 dq;print "MSG=" dq $14 dq }' dq='"')
MENUSTR="\"$J $HOST_NAME($IP,$COUNTRYSHORT)\" \
\"$SCORE|$PING|$SPEED|$NUMVPNSESSION\" \
\"Uptime:$UPTIME Users:$TOTALUSERS Traffic:$TOTALTRAFFIC Log:$LOGTYPE\" \\"
echo "$MENUSTR" >> "vpnmenu.txt"
done
IFS=$IFS_
Его удалось оптимизировать до такого, без использования цикла и переменных bash:
cat data/servers | awk -F, \
'{
HOST_NAME = $1;
IP = $2;
SCORE = $3;
PING = $4;
SPEED = $5;
COUNTRY = $6;
COUNTRYSHORT = $7;
NUMVPNSESSION = $8;
UPTIME = $9;
TOTALUSERS = $10;
TOTALTRAFFIC = $11;
LOGTYPE = $12;
OPERATOR = $13;
MSG = $14;
printf \
"\"%i %s(%s,%s)\" \"%s|%s|%s|%s\"" \
" \"Uptime:%s Users:%s Traffic:%s Log:%s\" \\\n",
++j, HOST_NAME, IP, COUNTRYSHORT, SCORE, PING, SPEED, NUMVPNSESSION,
UPTIME, TOTALUSERS, TOTALTRAFFIC, LOGTYPE;
}' > vpnmenu.txt
Но в данном случае мне просто повезло, нужно было перекодировать данные из одного формата в другой.
Это репост с сайта http://tolik-punkoff.com
Оригинал: http://tolik-punkoff.com/2020/01/21/zapi