📜 ⬆️ ⬇️

Zsh: fucky new year

I read the post habrahabr.ru/post/247161 and thought: here is a man wrote an incomprehensible program on bash, which displays "Happy new year". But this is bash! It is necessary to show that zsh is not worse, and even much better! And so, a zsh program that displays “Happy New Year!” (In Russian!) With the following restrictions:
  1. The program should not use any third-party programs. Neither base64, nor cat, nothing.
  2. The program should display the text in Russian.
  3. The program should be written in ASCII, but should not contain a single letter or number.
I don’t know how I would handle bash, but zsh is easier:

Zsh has variable expansion options for Parameters Expansion Flags from man zshexpn ): echo ${(#):-65} will show you the Latin letter “A”. Works with current locale. In principle, this is enough for writing the necessary program, but there are other knowledge that greatly facilitate life:

Firstly, there are anonymous functions, so you don’t need to invent names for functions (although you can safely even call a function + ), and you can also get an additional array @ (and not one, but only one in one scope).
')
Secondly, wherever zsh expects a number, you can use arithmetic expansion (Arithmetic Expansion from the same man zshexpn , more details in the ARITHMETIC EVALUATION section in man zshmisc ), which eliminates the spelling of $(()) , $[] , yes and just $ . Including the example above can be written as V=0x41; echo ${(#):-V+(VV)} V=0x41; echo ${(#):-V+(VV)} , which also applies to values ​​within indices (useful when using $@ ).

Third, a procedure like ${(#)} can be done with an array, with (#) applied to each element of the array.

Fourthly, if you need to apply several transformations in succession, then you do not need a temporary variable: ${${(#)@}// } quite successfully converts the array of arithmetic expressions given in the arguments into one line without spaces (two transformations : (#) and removal of spaces). You do not need a temporary variable for the transformations above the lines: ${:-string} expanded in string , although there are no variables here (the variant was used above). Bash and in general all other shells cannot do this.

Thus, we get the following code:
  1 (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___)) 2 _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____))) 3 ______=$((_______+__+__<<____)) 4 \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { <<< $@ ; <<< $(\* $[$@+____]) } } 5 +(){<<< "${${(#)@}// }"} 6 (){ 7 (){<<< "$@"} \ 8 "$(+ _______)" \ 9 "$(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___)" \ 10 "$(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__')" \ 11 } $(\* $[______]) 
. Here in the first line we declare variables with the values ​​1, 2, 4, 8, in the second with the value 0x0421 (U + 0421 is the CYRILLIC CAPITAL LETTER ES), in the third - 0x0432 (CYRILLIC SMALL LETTER VE). In the fourth line, the recursive function that generates a sequence of numbers in increments of 4, in the fifth line, the function practically considered above turns the array of arithmetic expressions into a string without spaces.

The anonymous function in the sixth line is needed in order to associate the numbers generated by the function from the fourth line with the array, in the seventh line - to merge several lines into one, separated by spaces. On the eleventh line, our recursive generator is called, and the rest is the text itself.

It seems that the problem is solved. Run:
 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 
. Oh, something is wrong here: there is a violation of the first condition. It's all about $NULLCMD : when there is no command, and we use redirection, the value of this variable is implicitly substituted, the default is cat . The solution is to create a cat function:
 cat() while { read i } { echo $i } 
(yes, the definition of a function without braces is correct, as is the loop with them, but without do / done ). Without eval this cannot be done, so the string being executed must be of the form eval 'cat()while {read i} {echo $i}' . A small problem here is that <<< can not be used, so you can not bother, but simply rewrite everything with another variable or function containing / returning echo . In the final program, this is the @ function:
 (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___)) ______=$[_____<<____-___<<____+____] ________="${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__}" @() $________ $________ _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____))) ______=$((_______+__+__<<____)) \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { $(@) $@ ; $(@) $(\* $[$@+____]) } } +() $(@) "${${(#)@}// }" (){ (){$(@) "$@"} \ "$(+ _______)" \ "$(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___)" \ "$(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__')" } $(\* $[______]) 
The penultimate step will get rid of quotes where you can:
 (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___)) ______=$[_____<<____-___<<____+____] ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__} @() $________ $________ _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____))) ______=$((_______+__+__<<____)) \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { $(@) $@ ; $(@) $(\* $[$@+____]) } } +() $(@) "${${(#)@}// }" (){ (){$(@) $@} \ $(+ _______) \ $(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___) \ $(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__') } $(\* $[______]) 
Minor minification, but something code painfully clear:
 (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$[___<<___] ______=$[_____<<____-___<<____+____] ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__} @()$________ $________ _______=$[__+(__<<(__+____))+(__<<(__+____))<<(__+____)] ______=$[_______+__+__<<____] \*(){((${@[-__]}<______+(______-_______)+_____))&&{`@` $@ `\* $[$@+____]` }} +()`@` "${${(#)@}// }" (){(){`@` $@} `+ _______` `+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___` `+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__'` } `\* $[______]` 
. Start, I will remind, env -i PATH= LANG=ru_RU.UTF-8 /bin/zsh -f script.zsh .

A variant that does not require a unicode locale (it is going to contain the line eval LANG=C , and with this locale, ${(#)} produces bytes with the specified value):
 (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$[___<<___] ______=$[_____<<____-___<<____+____] ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__} @()$________ $________ ^()`@` ${(#):-$@[__]}${(#):-$@[___]} ''(){(($@<(_____<<____)))&&`@` ${(#)@}||^ $[($@)>>(____+___)|(_____<<____+_____<<(___+__))] $[($@)&(__<<(____+___)-__)|(_____<<____)]} _______=$[__+(__<<(__+____))+(__<<(__+____))<<(__+____)] ______=$[_______+__+__<<____] \*(){((${@[-__]}<${@[__]}))&&{`@` $[$@[-__]] `\* $@[__] $[$@[___]+____]` }} +(){(($#))&&`@` $('' $@[__])$(+ $@[___,-__])} /()`@` "${${(#)@}// }" `() {/ $@[__] $@[-__]+____+__ $@[__]-____ $@[-___]-__} $(\* $[#________+(_____<<__)] $[#________])` \ `() {/ $@[-__] $@[__]+__ $@[-__]+___ $@[___+__]-__} $(\* '(____<<____)+_____*___' '____<<____')`=${(#):-$[_____<<(___+__)+___+__]} (){(){`@` $@} `+ _______` `+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___` `+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__'` } `\* '______+(______-_______)+_____' ______` 
.

If you add the restrictions "no fork" and "no underscores", then the task will be more interesting. In this case, there are a few more tricks to remember: first, the attempt to execute the unknown command ends with a return code of 127, which will be in the $? variable $? . &>‐ this prevents the error message from being printed: the /()+++++++&>- function /()+++++++&>- equivalent to ?=127 (if, of course, you could assign this variable) unless you do not have the +++++++ command +++++++ . ${#?} will then be equal to three. Also, you can use /()''&>- : will this option always give you 126 in $? since you cannot execute a directory (note: you can define a function with an empty name, which makes it impossible to use this trick). The last trick: the /(). /().&>- function /().&>- $? 1, as there is an attempt to call . without arguments, a /(). ''&>- /(). ''&>- because . will try to find a file with an empty name in $PATH , and, of course, will not (if you add a file to $PATH instead of a directory, you will receive the same return code with the message “This is not a directory”:. looking for path_item/ , and having / in the end leads to this result).

Secondly, the variable $! contains the PID of the last process running in the background. Or 0, if nothing in the background was launched (although the last part is not described in the documentation).

Thirdly, although in the article to which I respond this trick was used, I haven’t used it yet: numbers can be collected by simple concatenation of strings. Including it is possible to collect them in the form of ${base}#${number} , which makes the task just elementary to generate: in $! we have 0, in $@ we have an array of one element (1), the number is collected in the form $[$@+$@]#$@$!$!$! will collect 2#1000 or 8 . If you don’t want to generate for any reason, then anonymous functions come in with the ability to assign $@ values ​​you need. Here is an example of code that performs eval LANG=C :
 /()+++&>- //()''&>- ///().&>- () { () { / () { / () { / ${(#):-$@[${##}<<${##}]+${##}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[${#}-${##}]-${#?}}${(#):-$@[$#]-${##}-${##}} $@[${##}] } $@[${##}] ${##}$!$! $[${##}$!$!+${##}$!] } ${(#):-$@[$#]+${?[-${##}]}-${##}}${(#):-$@[${#}-${##}]+${?[${##}]}+${##}<<${?[${##}+${##}]}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[$#]+${##}}=${(#):-$@[$#]-${#?}} $@[${##},${#?}] } $@ $[$@[$#]+${#}-${##}]$! $[$@[$#]+${#}]$! } ${#?} $[${#?}+${#?}] $[${#?}<<(${#?}+${#?})] 


Here is an example of a one-liner in Python, which, using the technology described above, will collect 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'))) 


Final program using generation for echo ! and manually assembling eval LANG=C :
 /()+++&>- //()''&>- ///(). ''&>- () { () { / () { / () { / ${(#):-$@[${##}<<${##}]+${##}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[${#}-${##}]-${#?}}${(#):-$@[$#]-${##}-${##}} $@[${##}] } $@[${##}] ${##}$!$! $[${##}$!$!+${##}$!] } ${(#):-$@[$#]+${?[-${##}]}-${##}}${(#):-$@[${#}-${##}]+${?[${##}]}+${##}<<${?[${##}+${##}]}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[$#]+${##}}=${(#):-$@[$#]-${#?}} $@[${##},${#?}] } $@ $[$@[$#]+${#}-${##}]$! $[$@[$#]+${#}]$! } ${#?} $[${#?}+${#?}] $[${#?}<<(${#?}+${#?})] : () { ${(#):-$[$[$@+$@]#$@$@$!$!$@$!$@]}${(#):-$[$[$@+$@]#$@$@$!$!$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!]}${(#):-$[$[$@+$@]#$@$@$!$@$@$@$@]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$!$!$!$!$@]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$!$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$@]}${(#):-$[$[$@+$@]#$@$!$!$!$@$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$!]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$@$!$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$!]}${(#):-$[$[$@+$@]#$@$!$!$!$!$@]} } ${#?} 

( : need to be guaranteed to have 0 in $? ). If you run strace , you will see that there is still a fork : when you start / , fork is done first, and then you can get over the possible locations of +++ . The same will come out with a '' , so you need to replace the function / with /// , which gives exactly the same result as / (not // !), But without fork .

Source: https://habr.com/ru/post/247249/


All Articles