Pull to refresh

Comments 37

Я однажды заинтересовался возможностями разбора history, в результате получил sudo для всей командной строки, включая пайпы и редиректы:

proceed_sudo () { sudor_command="`HISTTIMEFORMAT=\"\" history 1 | sed -r -e 's/^.*?sudor//' -e 's/\"/\\\"/g'`" ; sudo sh -c "$sudor_command"; }; alias sudor="proceed_sudo # "

Использовать так:

$ sudor make me a sandwitch > /var/lib/sandwitch

Несерьезно, но забавно. И нельзя вставлять в пайп.
Залез в свои .bash*, нашел кучу старых добрых, но забытых вещей :)

Вот например, итерация по чему-нибудь с прогресс-метками:

finit() { count=$#; current=1; for i in "$@" ; do echo $current $count; echo $i; current=$((current + 1)); done; } alias fnext='read cur total && echo -n "[$cur/$total] " && read'

И использование:

finit 1 2 3 4 | while fnext item; echo $item ; done

Пример:

$ finit 1 2 3 4 | while fnext item; do echo $item; done
[1/4] 1
[2/4] 2
[3/4] 3
[4/4] 4
Интересно, когда это sed успел научиться нежадными множителями? Небольшой опыт показывает, что .*? ничем не отличается от .*. Точнее от (.*)?, за исключением сохранения текста.

В zsh можно такое устроить переопределяя accept-line widget. Тоже правда выглядит как хак, особенно вместе с функцией, которая засовывает в историю исходный текст, а не то, что будет реально выполнено. Как мне кажется, возможность работы echo abc | sudor { read def && echo $def } | read ghi можно обеспечить в обоих случаях если немного заморочиться.
Вставлю свои «5 копеек»:
a="echo a";
$a

В таком виде только в zsh возникают «нюансы»: команды «echo a» не существует.
Надёжнее делать:
a="echo a";
eval $a

С использованием eval проблем нет ни в одном из шеллов (ну, насколько мне известно...;)

При использовании команды sudo в скриптах, почти всегда лучше использовать ключ -i: в неинтерактивном режиме, большая часть шеллов не source'ит rc и profile файлы, что может привести к проблемам в работе софта.

Попытаюсь вспомнить ещё чего-нибудь «забавного» и допишу.
Полезный маленький трюк:
cd - 
cd $OLDPWD 

делают одно и то же. Последнюю переменную можно использовать для всяких mv дополнительно.

Ещё мелкая «полезняшка», экономящая время на длинных именах файлов:
 mv file{,.bak} 


На время отладки можно добавить ключ -x к шабангу:
 #!/bin/bash -x

Эквивалентно set -x.
Ну это больше из популярных bash tips and tricks. Я старался показать неочевидные вещи. (:
eval стоит использовать с огромной осторожностью. Некоторые любят устроить публичную порку за его использование, дескать это пропасть в безопасности, за такое руки отрубать надо (:
UFO just landed and posted this here
Помню, как то нужно мне было сделать перебор от 1 до нужного мне числа. Вот тогда, я и познакомился с «граблями» в Bash-е )

Вот пример того, как Bash с аппетитом начнет «кушать» вашу RAM:
#!/bin/bash

for i in {1..10000000}; do
    echo "item: $i";
done

Итого, скрипт взял на себя 1831 Mb.

Чуть позже, нашел другой вариант, «полегче»:

#!/bin/bash

for i in $(seq 10000000); do
    echo "item: $i";
done

Тут уже, почти в 2 раза меньше. Всего 1049 Mb. Но все равно, мне как то, не посебе, когда на такое, уходит столько памяти.
Так а что вы удивляетесь? Первым делом будет раскрываться seq и создаваться безумно длинная строка «for i in перечисление всех-всех чисел», которая будет храниться в памяти. Надо было делать
seq 10000000 | while read i ; do echo "item: $i"; done
проблем с потреблением памяти не возникло бы.
Традиционно:

seq 100000|xargs -n 1 echo item
В свое время, этого не знал, а в примерах, в основном, только вышеперечисленные варианты.
Используя такую конструкцию, данной проблемы можно избежать:

for ((i=0;i<10000000;i++))
do
    echo "item: $i"
done
UFO just landed and posted this here
Потому что это спецсимвол регулярного выражения.
Вам здесь grep ни к чему, используйте fgrep и быстрее, и экранировать не надо будет.
UFO just landed and posted this here
Может кому пригодится:
Меняем формат мак адреса с 0000.0000.0000 (cisco like) на 00:00:00:00:00:00:
echo "0000.0000.0000" |sed -e 'y/./:/' | sed -r 's/([0-9,a-f]{2})([0-9,a-f]{2})/\1:\2/g'


Зачем запускать sed два раза? Засуньте обе команды в один, разделив точкой с запятой.
Хозяйке на заметку: если надоело выискивать очередной подводный камень шелла, перепишите скрипт на перле.
На Перле — не сильно. Сильно проще на Tcl.
Крайне мало знаю про Tcl, но думаю, что вероятность встретить его в дикой природе несколько меньше чем вероятность встретить перл, идущий зависимостью к каждой второй софтине.
2й пункт. Да вы что !??!

$ while read line; do X=$line; done < <(cat /etc/hosts | head)
$ echo $X
# IPv4 and IPv6 localhost aliases


Проблема subshell'а решается через именованый пайп на раз-два!
Башизм же. Я старался обойтись чем-нибудь POSIX совместимым.
Ну и что? Это просто альяс к именованым пайпам. Вот на чистом злом sh, на FreeBSD:

$ [ ! -e pipe ] && mkfifo pipe
$ trap 'rm -f pipe' EXIT             
$ cat /etc/hosts | head > pipe &
$ while read line; do X=$line; done <pipe
$ echo $X
# IPv4 and IPv6 localhost aliases
в 3м пункте лучше отправить в 3-й файловый дескриптор все же, не трогайте stderr :).
Да вообще пример не очень — с sed'ом можно и без файловых дескрипторов обрабатывать только то, что нужно или то, что не нужно :)
 { echo DATA ; echo HIDDEN_DATA; } | sed -e '/HIDDEN/!s/^/MODIFIED_/g' -e 's/$/_CATCHED/'
[[ 2 -eq 3 ]]
[[ "test" == "test" ]]
[[ $VAR -eq 3 ]]

Поначалу с виду подумал, что это пауэршелльный код, только следующее примечание про Баш всё расставило по местам.
насчет строк в test надо аккуратнее — в случае нескольких строк ( а так бывает если вывод команды ) типа
VAR=`some args`
if [ -n "$VAR" ]; then ...
   ....
fi 

стоит обратить внимание на кавычки вокруг $VAR

А вообще shell скриптинг — это для внимательных к деталям и сахарку в неожиданных местах. Перлисты поймут.
Но регулярные извраты что бы получить простую в классическом ЯП обработку вызывают смешанные чувства.
меня тоже смутил второй пункт.
часто достаточно передать файл на вход while, и пайп (и собственно сабшелл) совершенно не нужен.
$ while read; do a=$((a + 1)); done < 1;

дополнение про mkfifo здесь, конечно, также очень полезно.

Расскажите, пожалуйста, в чем разница между [[ ]] и [ ], который test?
Беглый просмотр man bash не помог.
Если вкратце, то [ — builtin и совместим со всеми шеллами. [[ — keyword, присутствует только в bash, ksh, zsh и им подобных. Так же применяются разные правила парсинга того, что внутри.
[ $a < $b ] # выдаст ошибку
[[ $a < $b ]] # корректно отработает
Более подробно можно почитать тут.
Sign up to leave a comment.

Articles