Zsh: fucky new year
Прочитал пост habrahabr.ru/post/247161/ и подумал: вот человек написал непонятную программу на bash, которая выводит «Happy new year». Но это ведь bash! Надо показать, что zsh не хуже, а даже намного лучше! И так, программа на zsh, выводящая «С новым годом!» (по‐русски!) со следующими ограничениями: Программа не должна использовать никакие сторонние программы. Ни base64, ни cat, ничего. Программа должна выводить текст по‐русски. Программа быть написана на ASCII, но не должна содержать ни одной буквы или цифры. Не знаю, как бы я справлялся на bash, но с zsh всё проще: У zsh есть параметры раскрытия переменных (Parameter Expansion Flags из man zshexpn): echo ${(#):-65} покажет вам латинскую букву «A». Работает с текущей локалью. В принципе, этого достаточно для написания нужной программы, но есть и другие знания, сильно облегчающие жизнь:
Во‐первых, существуют анонимные функции, благодаря чему не нужно выдумывать имена для функций (хотя функцию можно спокойно назвать даже +), а также можно получить дополнительный массив @ (и не один, но только один в одной области видимости).
Во‐вторых, везде, где zsh ожидает число, можно использовать арифметическое раскрытие (Arithmetic Expansion из того же man zshexpn, более подробно в секции ARITHMETIC EVALUATION в man zshmisc), что избавляет от написания $(()), $[], да и просто $. В том числе пример выше можно написать как V=0×41; echo ${(#):-V+(V-V)}, что применимо и к значениям внутри индексов (пригодится при использовании $@).
В‐третьих, процедуру вроде ${(#)} можно проделать с массивом, при этом (#) применится к каждому элементу массива.
В‐четвёртых, если вам нужно применить последовательно несколько преобразований, то вам не нужна временная переменная: ${${(#)@}// } вполне успешно преобразовывает массив арифметических выражений, данных в аргументах, в одну строку без пробелов (два преобразования: (#) и удаление пробелов). Вам не нужна временная переменная и для преобразований над строками: ${:-string} раскрывается в string, хотя никаких переменных здесь нет (вариант использовался выше). Bash и вообще все остальные оболочки так не могут.
Таким образом, получаем следующий код:
1 (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___)) 2 _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____))) 3 ______=$((_______+__+__<<____)) 4 \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { <<< $@ ; <<< $(\* $[$@+____]) } } 5 +(){<<< "${${(#)@}// }"} 6 (){ 7 (){<<< "$@"} \ 8 "$(+ _______)" \ 9 "$(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___)" \ 10 "$(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__')" \ 11 } $(\* $[______]) . Здесь в первой строке объявляем переменные со значениями 1, 2, 4, 8, во второй со значением 0x0421 (U+0421 это CYRILLIC CAPITAL LETTER ES), в третьей — 0x0432 (CYRILLIC SMALL LETTER VE). В четвёртой строчке рекурсивная функция, генерирующая последовательность чисел с шагом 4, в пятой — практически рассмотренная выше функция, превращающая массив арифметических выражений в строку без пробелов.Анонимная функция в шестой строчке нужна для того, чтобы связать сгенерированные функцией из четвёртой строчки числа с массивом, в седьмой — для объединения нескольких строк в одну, разделённую пробелами. На одиннадцатой строчке вызывается наш рекурсивный генератор, а в остальных находится сам текст.
Кажется, задача решена. Запускаем:
env -i PATH= LANG=ru_RU.UTF-8 /bin/zsh -f script.zsh *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat *: command not found: cat +: command not found: cat +: command not found: cat +: command not found: cat (anon): command not found: cat . Ой, что‐то здесь не так: налицо нарушение первого условия. Всё дело в $NULLCMD: когда команды нет, а мы используем перенаправление, неявно подставляется значение этой переменной, по‐умолчанию равное cat. Решением может служить создание функции cat: cat () while { read i } { echo $i } (да, определение функции без фигурных скобок корректно, как и цикл с ними, но без do/done). Без eval такое сделать не получится, поэтому выполняемая строка должна иметь вид eval 'cat ()while {read i} {echo $i}'. Небольшая проблемка тут в том, что <<< использовать нельзя, так что можно и не заморачиваться, а просто переписать всё с ещё одной переменной или функцией, содержащей/возвращающей echo. В итоговой программе это функция @: (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___)) ______=$[_____<<____-___<<____+____] ________="${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__}" @() $________ $________ _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____))) ______=$((_______+__+__<<____)) \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { $(@) $@ ; $(@) $(\* $[$@+____]) } } +() $(@) "${${(#)@}// }" (){ (){$(@) "$@"} \ "$(+ _______)" \ "$(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___)" \ "$(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__')" } $(\* $[______]) Предпоследним шагом избавимся от кавычек где можно: (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___)) ______=$[_____<<____-___<<____+____] ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__} @() $________ $________ _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____))) ______=$((_______+__+__<<____)) \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { $(@) $@ ; $(@) $(\* $[$@+____]) } } +() $(@) "${${(#)@}// }" (){ (){$(@) $@} \ $(+ _______) \ $(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___) \ $(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__') } $(\* $[______]) Небольшая минификация, а то что‐то код больно понятный: (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$[___<<___] ______=$[_____<<____-___<<____+____] ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__} @()$________ $________ _______=$[__+(__<<(__+____))+(__<<(__+____))<<(__+____)] ______=$[_______+__+__<<____] \*(){((${@[-__]}<______+(______-_______)+_____))&&{`@` $@ `\* $[$@+____]` }} +()`@` "${${(#)@}// }" (){(){`@` $@} `+ _______` `+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___` `+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__'` } `\* $[______]` . Запуск, напомню, env -i PATH= LANG=ru_RU.UTF-8 /bin/zsh -f script.zsh.