Zsh: fucky new year

    I read the post habrahabr.ru/post/247161 and thought: here a man wrote an incomprehensible program in bash that displays “Happy new year”. But this is bash! We must show that zsh is not worse, but even much better! And so, the program on zsh, displaying "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 must be written in ASCII, but must not contain a single letter or number.
    I don’t know how I would handle bash, but with zsh everything is simpler:

    zsh has parameters for expanding variables (Parameter Expansion Flags from man zshexpn ): it echo ${(#):-65}will show you the Latin letter “A”. Works with the current locale. In principle, this is enough to write the desired program, but there are other knowledge that makes life much easier:

    Firstly, there are anonymous functions, so you do not need to invent names for functions (although you can even name a function +), and you can also get additional an array @(and not one, but only one in one scope).

    Secondly, where zsh expects a number, you can use arithmetic expansion (Arithmetic Expansion from the same man zshexpn, in more detail in the ARITHMETIC EVALUATION section inzshmisc man ), which eliminates the writing $(()), $[]and simply $. Including the example above, you can write how V=0x41; echo ${(#):-V+(V-V)}, which applies to values ​​inside indices (useful when used $@).

    Thirdly, the procedure seems to ${(#)}be done with the array, while (#)applying to each element of the array.

    Fourth, if you need to apply several conversions sequentially, then you do not need a temporary variable: ${${(#)@}// }it quite successfully converts an array of arithmetic expressions given in arguments into one line without spaces (two conversions: (#)and removing spaces). You do not need a temporary variable for conversions over strings either: ${:-string}expanded instringalthough there are no variables here (the option was used above). Bash and generally 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 the variables with values ​​1, 2, 4, 8, in the second with the value 0x0421 (U + 0421 is CYRILLIC CAPITAL LETTER ES), in the third - 0x0432 (CYRILLIC SMALL LETTER VE). In the fourth line, a recursive function that generates a sequence of numbers in increments of 4, in the fifth line, the function almost considered above, which turns an 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 - to combine several lines into one, separated by spaces. On the eleventh line, our recursive generator is called, and the rest contains the text itself.

    It seems that the problem is solved. We launch:
    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. The thing is $NULLCMD: when there is no command, but we use redirection, the value of this variable is implicitly substituted, by default it is equal to cat. The solution can be to create a function cat:
    cat()
        while { read i } {
            echo $i
        }
    
    (yes, the definition of a function without curly brackets is correct, as is the loop with them, but without do/ done). Without evalthis, you can’t do this, so the line you execute should look like this eval 'cat()while {read i} {echo $i}'. A small problem here is that <<<you can’t use it, so you don’t have to bother, but simply rewrite everything with another variable or function that contains / returns echo. In the final program, this is a function @:
    (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___))
    ______=$[_____<<____-___<<____+____]
    ________="${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__}"
    @() $________ $________
    _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____)))
    ______=$((_______+__+__<<____))
    \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { $(@) $@ ; $(@) $(\* $[$@+____]) } }
    +() $(@) "${${(#)@}// }"
    (){
        (){$(@) "$@"} \
            "$(+ _______)" \
            "$(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___)" \
            "$(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__')"
    } $(\* $[______])
    
    The penultimate step is to get rid of quotes where you can:
    (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$((___<<___))
    ______=$[_____<<____-___<<____+____]
    ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__}
    @() $________ $________
    _______=$((__+(__<<(__+____))+(__<<(__+____))<<(__+____)))
    ______=$((_______+__+__<<____))
    \*(){(( ${@[-__]} < ______+(______-_______)+_____ )) && { $(@) $@ ; $(@) $(\* $[$@+____]) } }
    +() $(@) "${${(#)@}// }"
    (){
        (){$(@) $@} \
            $(+ _______) \
            $(+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___) \
            $(+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__')
    } $(\* $[______])
    
    A little minification, but something code is painfully clear:
    (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$[___<<___]
    ______=$[_____<<____-___<<____+____]
    ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__}
    @()$________ $________
    _______=$[__+(__<<(__+____))+(__<<(__+____))<<(__+____)]
    ______=$[_______+__+__<<____]
    \*(){((${@[-__]}<______+(______-_______)+_____))&&{`@` $@ `\* $[$@+____]` }}
    +()`@` "${${(#)@}// }"
    (){(){`@` $@} `+ _______` `+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___` `+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__'` } `\* $[______]`
    
    . Launch, recall env -i PATH= LANG=ru_RU.UTF-8 /bin/zsh -f script.zsh.

    An option that does not require a unicode locale (a line is collected in it eval LANG=C, but with this locale it ${(#)}produces bytes with a given value):
    (){__=$# } !;___=$[__<<__];____=$[__<<___];_____=$[___<<___]
    ______=$[_____<<____-___<<____+____]
    ________=${(#):-______+__}${(#):-______-__}${(#):-______+____}${(#):-______+_____+___+__}
    @()$________ $________
    ^()`@` ${(#):-$@[__]}${(#):-$@[___]}
    ''(){(($@<(_____<<____)))&&`@` ${(#)@}||^ $[($@)>>(____+___)|(_____<<____+_____<<(___+__))] $[($@)&(__<<(____+___)-__)|(_____<<____)]}
    _______=$[__+(__<<(__+____))+(__<<(__+____))<<(__+____)]
    ______=$[_______+__+__<<____]
    \*(){((${@[-__]}<${@[__]}))&&{`@` $[$@[-__]] `\* $@[__] $[$@[___]+____]` }}
    +(){(($#))&&`@` $('' $@[__])$(+ $@[___,-__])}
    /()`@` "${${(#)@}// }"
    `() {/ $@[__] $@[-__]+____+__ $@[__]-____ $@[-___]-__} $(\* $[#________+(_____<<__)] $[#________])` \
        `() {/ $@[-__] $@[__]+__ $@[-__]+___ $@[___+__]-__} $(\* '(____<<____)+_____*___' '____<<____')`=${(#):-$[_____<<(___+__)+___+__]}
    (){(){`@` $@} `+ _______` `+ ${@[____]}-__ ${@[____]} ______ ${@[-__]}+__ ${@[____]}-___` `+ ${@[__]}+__ ${@[____]} ${@[__]}+___ ${@[____]} ${@[____]}-___ '____<<(____-__)+__'` } `\* '______+(______-_______)+_____' ______`
    
    .

    If you add the restrictions “no fork” and “no underscores”, then the task will be more interesting. In this case, you will have to recall a few more tricks: firstly, an attempt to execute an unknown command ends with a return code of 127, which will turn out to be in a variable $?. &>‐this will prevent the printing of an error message: the function is /()+++++++&>-equivalent ?=127(if, of course, you could assign to this variable), unless you have a command +++++++. ${#?}then it will be equal to three. More, you can use /()''&>-: this option will always lead you 126in $?, because you perform the directory can not (note: you can define a function with an empty name that will make it impossible to use this trick). Last Trick: Feature/().&>-will assign $?1, since here there is an attempt to call .without arguments, but /(). ''&>-, since .it will try to find a file with an empty name in $PATH, and, of course, it cannot (if you add to the $PATHfile instead of the directory, you will get the same return code with the message “This is not a directory”: it .searches path_item/, and the presence /at 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 started (though the last part is not described in the documentation).

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


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


    The final program using generation for echo С новым годом!and manual assembly eval LANG=C:
    /()+++&>-
    //()''&>-
    ///(). ''&>-
    () {
        () {
            /
            () {
                /
                () {
                    /
                    ${(#):-$@[${##}<<${##}]+${##}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[${#}-${##}]-${#?}}${(#):-$@[$#]-${##}-${##}} $@[${##}]
                } $@[${##}] ${##}$!$! $[${##}$!$!+${##}$!]
            } ${(#):-$@[$#]+${?[-${##}]}-${##}}${(#):-$@[${#}-${##}]+${?[${##}]}+${##}<<${?[${##}+${##}]}}${(#):-$@[$#]+${##}<<${#?}}${(#):-$@[$#]+${##}}=${(#):-$@[$#]-${#?}} $@[${##},${#?}]
        } $@ $[$@[$#]+${#}-${##}]$! $[$@[$#]+${#}]$!
    } ${#?} $[${#?}+${#?}] $[${#?}<<(${#?}+${#?})]
    :
    () {
        ${(#):-$[$[$@+$@]#$@$@$!$!$@$!$@]}${(#):-$[$[$@+$@]#$@$@$!$!$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!]}${(#):-$[$[$@+$@]#$@$@$!$@$@$@$@]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$!$!$!$!$@]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$!$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$@]}${(#):-$[$[$@+$@]#$@$!$!$!$@$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$!]} ${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$!$@$@]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$!$@$!$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$@$!]}${(#):-$[$[$@+$@]#$@$@$!$@$!$!$!$!]}${(#):-$[$[$@+$@]#$@$!$@$@$@$@$!$!]}${(#):-$[$[$@+$@]#$@$!$!$!$!$@]}
    } ${#?}
    

    ( :needed in order to be guaranteed to have 0c $?). If you run strace, you will see what forkis still there: when you start /it is done first fork, and only then possible locations are sorted out +++. The same thing will happen with '', so you need to replace the function /with ///, giving absolutely the same result as /(not //!), But without fork.

    Also popular now: