12
\$\begingroup\$

Adapted from tips for restricted-source in Python

Just like , pushes one to exploit quirks and hidden features of the zsh language. While we already have a place to collect all these for , those for remain transmitted by word of mouth or hidden deep within the manual pages.

So, what are some for solving challenges in zsh?

Please only include 1 tip per answer. Please downvote answers with multiple tips in them.


What makes a good tip here? There are a couple of criteria I think a good tip should have:

  1. It should be (somewhat) non obvious.

    Similar to the tips, it should be something that someone who has golfed in zsh (or bash) a bit and read the tips page would not immediately think of. For example, "replace a + b with a+b to avoid using spaces" is obvious to any golfer since it is already a way to make your code shorter and thus not a good tip.

  2. It should not be too specific.

    Since there are many different types of source restrictions, answers here should be at least somewhat applicable to multiple source restrictions, or one common source restriction. For example tips of the form "How to X without using character(s) Y" are generally useful since banned characters is a common source restriction. The thing your tip helps to do should also be somewhat general. For example tips of the form "How to create numbers with X restriction" are useful since many programs utilize numbers regardless of the challenge. Tips of the form "How to implement Shor's algorithm with X restriction" are basically just answers to a challenge you just invented and not very helpful to people solving other challenges.

\$\endgroup\$
0

5 Answers 5

5
\$\begingroup\$

Globbing

Globs are patterns used to match files and other strings. If you can't use a particular character but you need to access a command with that character in its name, you can often approximate it with a glob, like /*/ls

The full description of glob syntax is under Filename Generation in zshexpn(1), but here is a summary:

  • ? matches any single character
  • * matches zero or more characters
  • <1-10> matches any number between 1 and 10
    • <-> matches any number
  • [a-d] matches any character in the set abcd
  • [:name:] matches any character in the named set (full details in the man-page)
  • [^a] or [!a] matches any character except a
    • [^] or [!] matches any single character
  • (a|b) matches a or b
  • With the option --nocaseglob, globbing is case-insensitive. This allows you to do /*/LS instead of ls
  • With the option --extendedglob, you can use the additional syntax:
    • ^pattern matches anything except pattern
    • a~b matches pattern a but not b
    • a~^b matches both patterns a and b
    • a# matches zero or more instances of a
    • a## matches one or more instances of a
\$\endgroup\$
4
\$\begingroup\$

Alternatives to common commands

  • echo can be replaced by:
    • print
    • <<<string (limited syntax)
    • printf %s (joins with no whitespace and doesn't have a trailing new-line)
  • printf and print -f are identical
  • ls can be replaced by:
    • dir -w1 is exactly the same
    • vdir is almost the same, except that its output format is different
    • echo *
    • echo ?# (requires --extendedglob)
  • cat can be replaced by:
    • <file or <&1 where 1 is a file descriptor (also shorter and does't use any external commands!)
    • cut -f1-
    • sed ""
    • dd (but has a different syntax)
    • tee (only for stdin)
    • grep $
    • head -X or tail -X, where X is known to be greater than the number of lines (or without a -X if X < 10)
    • fold -X, where X is known to be greater than the maximum line length (or without a -X if X < 80)
    • expand, when there are known to be no spaces
    • tac, when there is only one line
    • tac|tac
    • rev, when there is only one character per line
    • rev|rev
    • sort, when the lines are known to be sorted
    • uniq, when there are known not to be any repeated lines
\$\endgroup\$
3
  • 2
    \$\begingroup\$ I'm a bit unsure how useful tac is going to be in place of cat, since it is just the same but backwards. \$\endgroup\$
    – Wheat Wizard
    Feb 22, 2021 at 14:34
  • \$\begingroup\$ @WheatWizard I was just going down the list of coreutils to pick out the suitable ones. But it might be useful for "your characters must be in X order" or something? \$\endgroup\$
    – pxeger
    Feb 23, 2021 at 17:50
  • \$\begingroup\$ I like using parameter expansion flags so that my answers tend to use shell builtins only. Zsh can do a lot without coreutils. E.g. (${(Oas::)1}) will parse the string in $1 into an array and reverse it \$\endgroup\$
    – roblogic
    Feb 24, 2021 at 2:32
2
\$\begingroup\$

Syntax in which (nearly) any character can be used

Yes, even control characters. Yes, even the null byte. Replace % in the following expressions:

  • Delimiters for PE flags: ${(s%foo%)1} (exceptions: one of < ( [ { must have the associated paired delimiter > ) ] })
  • Similarly, delimiters for the F, W, and s modifiers: ${1:F%expr%}, ${1:s%str%repl} (same exceptions)
  • Zsh's equivalent to ord (as already mentioned): $[##%] (exceptions: ] must be \-escaped, as does \)
\$\endgroup\$
3
  • \$\begingroup\$ In the first two, you can't use }, and if you use one of <([{ then the second % has to be replaced with the matching closing bracket. \$\endgroup\$
    – pxeger
    Jul 30, 2021 at 15:29
  • \$\begingroup\$ The third one doesn't accept ] or \​ unless escaped with a backslash \$\endgroup\$
    – pxeger
    Jul 30, 2021 at 15:32
  • \$\begingroup\$ Thanks, added the caveats! I'll make sure to add more answers here if I think of any other tools I've used in the past. \$\endgroup\$ Jul 31, 2021 at 16:10
1
\$\begingroup\$

ASCII character conversion

zsh has no explicit chr and ord functions, but they are achievable in various ways.

ord:

  • $[#A] - the value of the first character in the variable A
  • $[##x] - the value of x
  • chars=({!..~}); $chars[(i)$A] - construct an ASCII array and find the index of a value in it (doesn't work for control characters, whitespace, or Unicode)

chr:

  • ${(#)A} - the character with the codepoint stored in the variable A
  • $'\157', $'\x6f' - literals with codepoint octal 157 or hexadecimal 6F
    • this syntax doesn't allow variable substitutions, but we could be use an eval, like eval "echo $'\\$A'"
  • echo \\157 - literal octal/hex escape sequences
    • can also use print or printf instead of echo
    • variable substitution is possible, so we can use a decimal value by converting to octal: \\$(printf %o $A) or \\$[[##8]A]
  • chars=({!..~}); $chars[A] - construct an ASCII array and index into it (doesn't work for control characters, whitespace, or Unicode)
\$\endgroup\$
2
  • 2
    \$\begingroup\$ It is worth adding \u as well, it can be used where \x isn't. Also note that these can be all be applied to command names as well. $'\145\x63\u68'${(#):-111} is echo, and can be used as such. \$\endgroup\$ Jul 30, 2021 at 13:41
  • 1
    \$\begingroup\$ $((#k)) is another way of getting ord value of k[1], when $[#k] doesn't work within brace expansion \$\endgroup\$
    – roblogic
    Nov 18, 2022 at 3:59
1
\$\begingroup\$

Variables without a $

Here are a variety of techniques for storing and accessing data without using the typical $variable expansion syntax.

Listing

The set builtin lists all variables and their values, which you can explicitly sift through to find the one you want. For example, set|grep my_var_name|cut -d= -f2-.

You can also use typeset, declare, local, float, or integer in place of set (those last two operate only on their namesake types, which are assigned implicitly when used in arithmetic expressions)

This may be unreliable if the variable is an array of if its value has special characters.

Named Directories

Detailed in zshexpn(1) § Filename Expansion, named directories are intended as shortcuts for typing whole paths in an interactive shell, but they work in scripts too.

"Assignment" is done with hash -d name=value, and expansion is done with ~name. The expansion only works when at the start of a whole word and can't be followed by anything except a /.

You can also use hash -dm name or hash -dv name which finds the value of name, or use hashs -d on its own to list all the values, and then find the right one using a similar technique to above.

Fake command names

Similar to named directories, but without the -d. The hash builtin would ordinarily add the value as the cached lookup path for the "command" name when it is looked up in $PATH.

Expansion can be done with one of:

  • which name
  • where name
  • whence name
  • hash, with -v or -m, as detailed above
  • command -v name
  • command -V name (has a weird locale-dependent prosaic output format which needs to be parsed)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.