може ще комусь стане в пригоді? тоді не полінуйтеся, будь ласка, зареєструватися на форумі й подякувати авторові =)
1. вступ
2. приклади
2.1. спосіб «sed+cut»
2.2. спосіб «лише bash»
3. швидкодія
3.1. тестовий скрипт
3.2. результати тестування швидкодії
4. заключне слово
1. вступ
що?
чимало скриптів bash часто потребують інформації з текстових змінних (або з виводу команд), приміром:
variable=" якийсь текст XXXX-YYYY-ZZ 0xab2345 траляля"
# ^^^ зауважте пробіли на початку текстового рядка
потребуючи й сам такого і шукаючи рецептів у тенетах, я помітив, що більшість пропонованих рішень використовують утиліти sed, cut та/або awk в різних комбінаціях. найчастіше текстовий рядок спрощують за допомогою sed, і згодом «вирізають» потрібну частину рядка за допомогою cut чи awk.
чому?
будучи впертим (уникаю використання python/ruby/perl) пуристом (навіщо викликати сторонні утиліти на кшалт sed у випадках, коли bash і сам достатньо потужний), я натрапив на згадку про «дивовижну кількість команд для операцій з текстовими рядками» та «масиви», інтегровані у bash, і відкрив для себе, що чимало операцій можна запрограмувати за допомогою цих команд замість вдаватися до запуску sed, cut і awk. а оскільки більшість пошукових запитів у google приводять саме на рецепти з sed/cut/awk, мені видалося непоганою ідеєю порекламувати інтегровані команди bash за допомогою кількох прикладів.як?
робиться це навдивовижу просто, і, на мою гадку, семантично елегантніше в порівнянні до рецептів з sed, cut і awk. є певні недоліки, зокрема деякі однорядні команди (one-liners) неможливо зконструювати в силу особливостей роботи команд bash. для обробки тексту і масивів, проте загальне враження таке, що використання інтегрованих команд bash робить скрипти більш зрозумілими для читання.далі йтимуть декілька прикладів…
2. приклади
гаразд, ось дуже простий приклад. я хочу витягти кожне чотирицифрове число в окрему змінну рядка на кшалт такого:
1680x1050+2880+23
звісно, неважко впізнати звичний рядок з «геометрією» дисплея, такі видає команда xrandr.
2.1 спосіб «sed+cut»
ось простий скрипт, щоби зробити це за допомогою sed:
# приклад SED
# задаймо текстовий рядок
xrandroutput="1680x1050+2880+23"
# визначмо символ табуляції TAB ('\t')
# знадобиться для sed та cut
TAB=$(echo -e "\t")
# замінімо «x» на тауляцію '\t'
array=`echo "$xrandroutput" | sed "s/x/$TAB/"`
# замінімо «+» на табуляцію '\t'
array=`echo -e "$array" | sed "s/+/$TAB/g"`
# збережімо потрібні значення
H=`echo -e "$array" | cut -f 1`
W=`echo -e "$array" | cut -f 2`
X=`echo -e "$array" | cut -f 3`
Y=`echo -e "$array" | cut -f 4`
гакери скрикнуть: «навіщо sed двічі?!» — і матимуть рацію, скрипт можна трошки скоротити:
# приклад SED
# задаймо текстовий рядок
xrandroutput="1680x1050+2880+23"
# визначмо символ табуляції TAB ('\t')
# знадобиться для sed та cut
TAB=$(echo -e "\t")
# замінімо «x» і «+» на тауляцію '\t'
array=`echo "$xrandroutput" | sed "s/[x+]/$TAB/g"`
# збережімо потрібні значення
H=`echo -e "$array" | cut -f 1`
W=`echo -e "$array" | cut -f 2`
X=`echo -e "$array" | cut -f 3`
Y=`echo -e "$array" | cut -f 4`
десь так… принаймні такі приклади я стрічав зазвичай. мабуть, це можна ще трохи оптимізувати, але суть не зміниться.
2.2 спосіб «лише bash»
а тепер обіцяний приклад використання лише інтегрованих команд bash:
# приклад ARRAY
# задаймо текстовий рядок
xrandroutput="1680x1050+2880+23"
# замінімо всі «x» на пробіли « », скориставшись
# потужною інтегрованою в bash командою заміни
# тексту за шаблоном:
# ${string//substring/replacement},
# і збережімо результат до змінної «array»
# (поки що це не масив)
array=${xrandroutput//x/" "} # результат: "1680 1050+2880+23"
# замінімо всі «+» на пробіли « » і збережімо
# результат тепер як масив у змінній «array»,
# використавши дужки «(» і «)»
# заувага: дужки приймають пробіл як роздільник
# і таким чином створюють масив значень
array=( ${array//+/" "} ) # результат: ( "1680" "1050" "2880" "23" )
# надрукуймо результат
echo "array[0] = ${array[0]}"
echo "array[1] = ${array[1]}"
echo "array[2] = ${array[2]}"
echo "array[3] = ${array[3]}"
звісно ж дві команди заміни можна об’єднати в одну:
# приклад ARRAY-SINGLE
...
# замінімо всі «x> і «+» на пробіли « »
# і збережімо результат в масив з назвою «array»
array=( ${xrandroutput//[x+]/" "} )
...
ну хіба ж не простіше і зрозуміліше? вже мовчу про те, що на виході всі дані маємо в одному масиві, а це досить зручно і не захаращує код назвами змінних.
аж зголоднів, поки писав це. мушу щось з’їсти і хильнути пива, перш ніж перейти до тестування швидкодії.
3. швидкодія
3.1 тестовий скрипт
чи справді варто використовувати інтегровані команди bash для обробки рядків і масивів, — а чи може це лише альтернативний спосіб і не більше? можна прогнати кілька тестів і подивитися, скільки за часом виконується кожен варіант. спершу я поясню, як саме я тестував. далі подаю чернетку тестового скрипта. ідея проста: запустити декілька (багато) подібних прикладів і заміряти час виконання.
#!/bin/bash
# використання: test [кількість_ітерацій]
iter=10000
# перевірмо, чи задано кількість ітерацій в командному рядку
if [ -n "$1" ]
then
iter="$1"
fi
TAB=$(echo -e "\t")
# проженімо ітерації
for i in `seq $iter`
do
#
# робочий код для тестування
#
done
цей тестовий скрипт запускається таким чином:
$ /usr/bin/time -f "\nReal: %E\nUser: %U\nSys: %S" ./test 100000
всередині циклу «do … done» вміщуємо тестовий код (я прибрав коментарі задля компактності):
# приклад SED
xrandroutput="1680x1050+2880+23"
array=`echo "$xrandroutput" | sed "s/x/$TAB/"`
array=`echo -e "$array" | sed "s/+/$TAB/g"`
H=`echo -e "$array" | cut -f 1`
а потім другий варіант:
# приклад ARRAY
xrandroutput="1680x1050+2880+23"
array=${xrandroutput//x/" "} # "1680 1050+2880+23"
array=( ${array//+/" "} ) # ( "1680" "1050" "2880" "23" )
зауважте, що у варіанті «приклад SED» ми змушені зберігати результат до змінної «H», тоді як у варіанті «приклад ARRAY» всі чотири значення зберігаються в одному масиві з доступом через «array[i]».
3.2 результати тестування швидкодії
щоби отримати надійний результат, я прогнав кожен тест кілька разів (міг би зробити це більш системно, підбивши якусь статистику, проте різниця настільки разюча, що в цім не було необхідности). далі я спробвав підібрати кількість ітерацій так, щоби скрипт виконувався приблизно 10 секунд, і таким чином нормалізувати результати.а результати такі:
приклад SED: 1000 ітерацій = 2,17 секунд
приклад ARRAY: 1000 ітерацій = 0,011 секунд
так, різниця 200-кратна на користь варіанту «приклад ARRAY». отже, інтегровані команди bash працюють значно швидше, ніж виклик sed.
дивно? ну, я очікував дещо вищої продуктивності, але ж не 200-кратно… насправді, тут я запрошую знавців написати оптимальніший код з використанням sed. можливо, це можна зробити краще, ніж вийшло у мене.
4. заключне слово
отже, чи варто користуватися інтегрованими командами bash? спробую підсумувати:переваги (аргументи на користь внутрішніх команд bash:
+ це значно швидше;
+ код зрозуміліший (на мою думку), з меншою кількістю змінних;
+ мені здається, що це простіше програмувати.
недоліки (проти):
– значна залежність від bash;
– хоча масиви і команди обробки рядків мають бути доступні в сучасних версіях bash (вище третьої), все-таки можуть виникати проблеми сумісності в старших версіях;
– неможливо реалізувати деякі однорядкові команди (one-liners), які досить просто отримати перенаправленням виводу.
наостанок кілька слів:
а) сподіваюся, комусь це стане в нагоді. розумію, що хтось скаже: «о, я це й так знав», інші запитають «про що це він взагалі говорить?», проте я таки сподіваюся, що хтось навчиться чогось нового;
б) перепрошую, що не спромігся пояснити коротше; також перепрошую за кострубату англійську;
в) не соромтеся збиткуватися з мене, якщо я десь тут припустився помилки.
кінець.