Ali1 писал(а): ↑13.11.2009 18:30
t.t писал(а): ↑13.11.2009 18:03
....Между ассемблером и лиспом? На мой взгляд, огромна. А ведь все три -- языки третьего поколения. Вот и между ними всеми (с одной стороны) и shell (с другой) разница не меньше. Об этом-то я и толкую.
Вот как раз хотел спросить Вас за Lisp. Мне показалось, что комментируя процесс написания dpkg -L .... Вы описали одну важную вещь. Вы постоянно рассуждали о данных, Вас не интересовали передачи управления и ... , т.е Вы рассуждали
непроцедурно( здесь непроцедурно не термин, а эпитет).
t.t, было бы интересно узнать, как Вы "мыслите" о переменных и циклах bash`а?
Очень хороший вопрос, спасибо.
Помните, у Реймонда: алгоритмы вторичны -- данные первичны? Да и не только у него, Реймонд сам цитирует Брукса: покажите мне код и скройте структуры данных -- я скорее всего ничесго не пойму в вашей программе; покажите структуры данных -- и код не понадобится.
Параллель с лиспом тоже к месту: это, пожалуй, один из самых высокоуровневых языков среди третьего поколения; и программирование, управляемое данными (data-driven programming) для него гораздо более естественный подход, чем для тех же процедурных языков.
Теперь собственно к Вашему вопросу. При решении задачи на shell как правило нет чёткой традиционной последовательности "задача - алгоритм - программа". Есть другая цепочка: "исходные данные - модифицированные данные - модифицированные данные - ... - результат". Это я и пытался проиллюстрировать выше, т.к. без Вашей подсказки не смог сформулировать. Т.е. сначала я знаю, какие данные есть на входе, либо какая команда выдаёт (в том числе и) те данные, которые мне нужны. Я смотрю на них -- и сразу вижу, какая первая команда мне нужна. Тут же могу запустить эту команду и посмотреть первый промежуточный результат -- и понять, какая будет вторая команда. И так по цепочке до окончательного результата, на каждом шагу видя и контролируя результат промежуточный. Это я и называю "на ходу".
Мне так мыслить проще. Приведу пример, сознательно чрезмерно упрощённый -- для наглядности: скажем, мне понятнее не "вывести третье поле тех строк, где первое равно единице" (уже алгоритм "в голове"), а "оставить строки, начинающиеся с единицы; оставить третье поле" -- т.е. "на автомате" я скорее всего напишу не так:
Shell
awk '$1="1" {print $3}'
а вот так:
Shell
grep "^1 " | cut -d ' ' -f 3
Таким образом "алгоритм" разбивается на цепочку простых и "как бы независимых" шагов -- "независимых" в том смысле, что мне я на каждом шагу вижу (действительно вижу, в смысле -- могу запустить и посмотреть) входные и выходные данные именно этого шага.
Цикл в этой цепочке возникает тогда, когда я понимаю, что на текущем шаге данные нужно обрабатывать, скажем, построчно (за тем исключением, когда обработка идёт с помощью sed или awk -- они делают это сами):
Shell
... | while read i; do ...; done | ...
или по словам:
Shell
for i in $(...); do ...; done | ...
или по файлам:
Shell
for i in *; do ...; done
Что касается переменных, то они бывают разные:
а) переменные окружения;
б) параметры командной строки (в том числе для внутренних функций);
в) специальные переменные, такие как, например, $# или $?;
г) "счётчики" циклов;
д) внутренние переменные скрипта или оболочки;
Думаю, для первых четырёх видов сами их названия говорят о смысле таких переменных. Для внутренних же переменных навскидку вспоминается только два применения.
Первое -- в качестве конфигурационных параметров. Тут два варианта: параметры могут задаваться либо в командной строке (пример в параллельной теме; в более сложных случаях лучше использовать getopt, но в повседневных задачах такие случаи практически не встречаются), либо в файле:
Shell
. $HOME/.config/$(basename $0)
Второе -- в качестве средства ветвления конвейеров или подстановок. Поскольку мы не можем писать что-то такое:
Shell
command1 \
command2 | command4
command3 /
или
Shell
command1 \
command2 $(command4)
command3 /
то приходится так (для второго случая):
Shell
a=$(command4)
command1 $a
command2 $a
command3 $a