- Программа не должна использовать никакие сторонние программы. Ни base64, ни cat, ничего.
- Программа должна выводить текст по‐русски.
- Программа быть написана на ASCII, но не должна содержать ни одной буквы или цифры.
У zsh есть параметры раскрытия переменных (Parameter Expansion Flags из man zshexpn):
echo ${(#):-65} покажет вам латинскую букву «A». Работает с текущей локалью. В принципе, этого достаточно для написания нужной программы, но есть и другие знания, сильно облегчающие жизнь:Во‐первых, существуют анонимные функции, благодаря чему не нужно выдумывать имена для функций (хотя функцию можно спокойно назвать даже
+), а также можно получить дополнительный массив @ (и не один, но только один в одной области видимости).Во‐вторых, везде, где zsh ожидает число, можно использовать арифметическое раскрытие (Arithmetic Expansion из того же
man zshexpn, более подробно в секции ARITHMETIC EVALUATION в man zshmisc), что избавляет от написания $(()), $[], да и просто $. В том числе пример выше можно написать как V=0x41; echo ${(#):-V+(V-V)}, что применимо и к значениям внутри индексов (пригодится при использовании $@).В‐третьих, процедуру вроде
${(#)} можно проделать с массивом, при этом (#) применится к каждому элементу массива.В‐четвёртых, если вам нужно применить последовательно несколько преобразований, то вам не нужна временная переменная:
${${(#)@}// } вполне успешно преобразовывает массив арифметических выражений, данных в аргументах, в одну строку без пробелов (два преобразования: (#) и удаление пробелов). Вам не нужна временная переменная и для преобразований над строками: ${:-string} раскрывается в string, хотя никаких переменных здесь нет (вариант использовался выше). Bash и вообще все остальные оболочки так не могут.Таким образом, получаем следующий код:
. Здесь в первой строке объявляем переменные со значениями 1, 2, 4, 8, во второй со значением 0x0421 (U+0421 это CYRILLIC CAPITAL LETTER ES), в третьей — 0x0432 (CYRILLIC SMALL LETTER VE). В четвёртой строчке рекурсивная функция, генерирующая последовательность чисел с шагом 4, в пятой — практически рассмотренная выше функция, превращающая массив арифметических выражений в строку без пробелов.1 (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___)) 2 _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____))) 3 ______=$((_______+__+__<<____)) 4 \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { <<< $@ ; <<< $(\* $[$@+____]) } } 5 +(){<<< "${${(#)@}// }"} 6 (){ 7 (){<<< "$@"} \ 8 "$(+ _______)" \ 9 "$(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___)" \ 10 "$(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__')" \ 11 } $(\* $[______])
Анонимная функция в шестой строчке нужна для того, чтобы связать сгенерированные функцией из четвёртой строчки числа с массивом, в седьмой — для объединения нескольких строк в одну, разделённую пробелами. На одиннадцатой строчке вызывается наш рекурсивный генератор, а в остальных находится сам текст.
Кажется, задача решена. Запускаем:
. Ой, что‐то здесь не так: налицо нарушение первого условия. Всё дело в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.Вариант, не требующий юникодной локали (в нём собирается строчка
eval LANG=C, а при этой локали ${(#)} выдаёт байты с заданным значением):.(){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$[___<<___] ______=$[_____<<____-___<<____+____] ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__} @()$________ $________ ^()`@` ${(#):-$@[__]}${(#):-$@[___]} ''(){(($@<(_____<<____)))&&`@` ${(#)@}||^ $[($@)>>(____+___)|(_____<<____+_____<<(___+__))] $[($@)&(__<<(____+___)-__)|(_____<<____)]} _______=$[__+(__<<(__+____))+(__<<(__+____))<<(__+____)] ______=$[_______+__+__<<____] \*(){((${@[-__]}<${@[__]}))&&{`@` $[$@[-__]] `\* $@[__] $[$@[___]+____]` }} +(){(($#))&&`@` $('' $@[__])$(+ $@[___,-__])} /()`@` "${${(#)@}// }" `() {/ $@[__] $@[-__]+____+__ $@[__]-____ $@[-___]-__} $(\* $[#________+(_____<<__)] $[#________])` \ `() {/ $@[-__] $@[__]+__ $@[-__]+___ $@[___+__]-__} $(\* '(____<<____)+_____*___' '____<<____')`=${(#):-$[_____<<(___+__)+___+__]} (){(){`@` $@} `+ _______` `+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___` `+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__'` } `\* '______+(______-_______)+_____' ______`
Если добавить ограничения «никакого fork» и «никаких подчёркиваний», то будет задача поинтереснее. В этом случае придётся вспомнить ещё несколько трюков: во‐первых, попытка выполнения неизвестной команды завершается с кодом возврата 127, который и окажется в переменной
$?. &>‐ при этом предотвратит печать сообщения об ошибке: функция /()+++++++&>- эквивалентна ?=127 (если бы, конечно, можно было присваивать этой переменной), если только у вас нет команды +++++++. ${#?} тогда окажется равной трём. Ещё, можно использовать /()''&>-: этот вариант всегда будет выдавать вам 126 в $?, поскольку выполнить каталог вы не можете (внимание: вы можете определить функцию с пустым именем, что сделает использование данного трюка невозможным). Последний трюк: функция /().&>- присвоит $? 1, так как здесь идёт попытка вызвать . без аргументов, а /(). ''&>-, т.к. . попытается найти файл с пустым именем в $PATH, и, разумеется, не сможет (если же вы добавите в $PATH файл вместо каталога, то получите тот же код возврата с сообщением «Это не каталог»: . ищет path_item/, и наличие / в конце приводит к такому результату).Во‐вторых, переменная
$! содержит PID последнего процесса, запущенного в фоне. Или 0, если ничего в фоне не запускалось (правда, последняя часть не описана в документации).В‐третьих, хотя в статье, на которую я отвечаю этот трюк использовался, я его пока не задействовал: числа можно собирать простой конкатенацией строк. В том числе можно собирать их и в виде
${base}#${number}, что делает задачу просто элементарной для генерации: в $! имеем 0, в $@ имеем массив из одного элемента (1), число собираем в виде $[$@+$@]#$@$!$!$! соберёт 2#1000 или 8. Если же генерировать по какой‐либо причине не хочется, то в дело вступают анонимные функции с возможностью присвоить $@ нужные вам значения. Вот пример кода, который выполняет eval LANG=C:/()+++&>- //()''&>- ///().&>- () { () { / () { / () { / ${(#):-$@[${##}<<${##}]+${##}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[${#}-${##}]-${#?}}${(#):-$@[$#]-${##}-${##}} $@[${##}] } $@[${##}] ${##}$!$! $[${##}$!$!+${##}$!] } ${(#):-$@[$#]+${?[-${##}]}-${##}}${(#):-$@[${#}-${##}]+${?[${##}]}+${##}<<${?[${##}+${##}]}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[$#]+${##}}=${(#):-$@[$#]-${#?}} $@[${##},${#?}] } $@ $[$@[$#]+${#}-${##}]$! $[$@[$#]+${#}]$! } ${#?} $[${#?}+${#?}] $[${#?}<<(${#?}+${#?})]
Вот пример однострочника на Python, который, используя описанную выше технологию, соберёт
echo С новым годом!:#!/usr/bin/env python3.4 print(''.join( ( ' ' if i == next(iter(b' ')) else '${{(#):-$[$[$@+$@]#{:b}]}}'.format(i).replace('0', '$!').replace('1', '$@') ) for i in 'echo С новым годом!'.encode('utf8')))
Итоговая программа, использующая генерацию для
echo С новым годом! и ручную сборку eval LANG=C:/()+++&>- //()''&>- ///(). ''&>- () { () { / () { / () { / ${(#):-$@[${##}<<${##}]+${##}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[${#}-${##}]-${#?}}${(#):-$@[$#]-${##}-${##}} $@[${##}] } $@[${##}] ${##}$!$! $[${##}$!$!+${##}$!] } ${(#):-$@[$#]+${?[-${##}]}-${##}}${(#):-$@[${#}-${##}]+${?[${##}]}+${##}<<${?[${##}+${##}]}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[$#]+${##}}=${(#):-$@[$#]-${#?}} $@[${##},${#?}] } $@ $[$@[$#]+${#}-${##}]$! $[$@[$#]+${#}]$! } ${#?} $[${#?}+${#?}] $[${#?}<<(${#?}+${#?})] : () { ${(#):-$[$[$@+$@]#$@$@$!$!$@$!$@]}${(#):-$[$[$@+$@]#$@$@$!$!$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!]}${(#):-$[$[$@+$@]#$@$@$!$@$@$@$@]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$!$!$!$!$@]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$!$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$@]}${(#):-$[$[$@+$@]#$@$!$!$!$@$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$!]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$@$!$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$!]}${(#):-$[$[$@+$@]#$@$!$!$!$!$@]} } ${#?}
(
: нужна для того, чтобы гарантированно иметь 0 в $?). Если вы запустите strace, то увидите, что fork всё же есть: при запуске / сначала делается fork, а уж потом перебираются возможные местонахождения +++. То же самое выйдет с '', поэтому нужно заменить функцию / на ///, дающую абсолютно тот же самый результат, что и / (не //!), но без fork.
