What Special Meaning Does an Equal-Sign Have in Zsh

What special meaning does an equal-sign have in zsh?

From the docs:

14.7.3 ‘=’ expansion

If a word begins with an unquoted ‘=’ and the EQUALS option is set,
the remainder of the word is taken as the name of a command. If a
command exists by that name, the word is replaced by the full pathname
of the command.

And here in more words

Zsh: Why is \n interpreted within single quotes?

zsh itself does not interpret the \n in 'a\nb', but the builtin echo does. 'a\nb' and "a\nb" are equivalent and do not contain a newline but the (literal) character sequence \n. $'a\nb' on the other hand actually does contains a newline.

There are two things at work here:

1. Quoting:

Quoting is used to tell the shell that you want a character itself, not any special meaning it may have in the shells syntax. zsh has four types of quoting, some of which may retain or add special meaning to a few characters or character sequences:

  1. \: Quoting of single characters by prepending \. For zsh saying \n only means "I want the character n". As n has no special meaning, it is the same as writing just n. This changes with characters like *: Without quoting * is used as a wildcard for globbing, writing \* prevents that (for example: compare outputs of echo /* and echo /\*). If you want to pass the character \ literally, you have to quote it, for example with another \: \\.
  2. '...': Just like with \ any character withing '...' is taken literally, this includes other methods of quoting. 'foo* bar?' is essentially equivalent to foo\*\ bar\? (or \f\o\o\*\ \b\a\r\? if one wants to be pedantic). Only ' itself cannot appear inside a string quoted with '...' as it would mark the end of the qoute. (This can be changed by setting RC_QUOTES. If set a pair of single quote is taken as a single quote: 'foo''bar'foo\'bar)
  3. "...": Allows for parameter and command substitution. That means words after a $ are taken as parameter names (e.g. "path: $PATH") and strings wrapped in $(...) or `...` are used for command substitution (e.g. echo "Date: $(date)"). Otherwise it behaves like '...' with the exception that it allows the quoting of `, $, \ and " with \.
  4. $'...': strings insid $'...' are treated like string arguments of the print builtin. Only here some alphabetic characters have indeed special meaning: \n for newline, \b for backspace, \a for the bell character, etc.. \ and ' can be quoted with \\ and \' respectively. The resulting string is considered fully quoted. There is no parameter or command substitution inside $'...'.

2. The zsh-builtin echo:

Other than the echo binary or the bash-builtin echo the zsh-builtin by default recognizes (some) escape sequences like \n or \t. While this behavior usually needs to be explicitly enabled with /bin/echo or the bash-builtin (usually by passing the -e flag: echo -e "foo\nbar"), for the zsh-builtin it need to be explicitly disabled. Either by passing the -E flag (echo -E 'foo\nbar') or by setting the BSD_ECHO option (setopt bsdecho) in which case the -e flag can be used to re-enable the feature like with the other types of echo.

Conclusion

That means that both 'a\nb' and "a\nb" (and a\\nb for that matter) are passed as a\nb (literally), but the zsh-builtin echo then interprets the \n, leading to an output with a newline. On the other hand $'a\nb' contains a literal newline already before it is passed to echo.

Running

for quoted_string in a\\nb 'a\nb' "a\nb" $'a\nb'; do
echo '>' $quoted_string '<'
/bin/echo -e '>' $quoted_string '<'
echo -E '>' $quoted_string '<'
/bin/echo '>' $quoted_string '<'
echo
done

should get you the following output:

> a
b <
> a
b <
> a\nb <
> a\nb <

> a
b <
> a
b <
> a\nb <
> a\nb <

> a
b <
> a
b <
> a\nb <
> a\nb <

> a
b <
> a
b <
> a
b <
> a
b <

As you can see there is no difference between the first three kinds of quoting, while the fourth always prints with a newline.


BTW: perl (at least version 5; I do not know about Perl 6) behaves in the way you describe the expected behavior. In perl '...' behaves like it does in zsh. On the other hand"..." in perl behaves like a combination of "..." and $'...' of zsh: variables are replaced by their value and character sequences like \n and \t are treated specially.

Confusion about EQUALS option in Zsh

setopt doesn't show values that currently have their default value for the current emulation mode. In Zsh emulation mode, EQUALS is on by default, so setopt will show only NO_EQUALS if the option is turned off.

As to why it is on by default? The author felt it was a useful enough feature to warrant doing so. There's no "logical" reason for one choice or the other.

What are the special dollar sign shell variables?

  • $1, $2, $3, ... are the positional parameters.
  • "$@" is an array-like construct of all positional parameters, {$1, $2, $3 ...}.
  • "$*" is the IFS expansion of all positional parameters, $1 $2 $3 ....
  • $# is the number of positional parameters.
  • $- current options set for the shell.
  • $$ pid of the current shell (not subshell).
  • $_ most recent parameter (or the abs path of the command to start the current shell immediately after startup).
  • $IFS is the (input) field separator.
  • $? is the most recent foreground pipeline exit status.
  • $! is the PID of the most recent background command.
  • $0 is the name of the shell or shell script.

Most of the above can be found under Special Parameters in the Bash Reference Manual. Here are all the environment variables set by the shell.

For a comprehensive index, please see the Reference Manual Variable Index.

How to define the = equals sign as a Bash alias?

Use a function:

=() { echo "foo"; }

Shell equality operators (=, ==, -eq)

= and == are for string comparisons

-eq is for numeric comparisons

-eq is in the same family as -lt, -le, -gt, -ge, and -ne

== is specific to bash (not present in sh (Bourne shell), ...). Using POSIX = is preferred for compatibility. In bash the two are equivalent, and in sh = is the only one that will work.

$ a=foo
$ [ "$a" = foo ]; echo "$?" # POSIX sh
0
$ [ "$a" == foo ]; echo "$?" # bash-specific
0
$ [ "$a" -eq foo ]; echo "$?" # wrong
-bash: [: foo: integer expression expected
2

(Note: make sure to quote the variable expansions. Do not leave out the double-quotes above.)

If you're writing a #!/bin/bash script then I recommend using [[ instead. The double square-brackets [[...]] form has more features, a more natural syntax, and fewer gotchas that will trip you up. For example, double quotes are no longer required around $a:

$ [[ $a == foo ]]; echo "$?"      # bash-specific
0

See also:

  • What's the difference between [ and [[ in Bash?

Check if the parameter named by the value of x is set

There's also the -v operator, which precludes the need for indirect parameter expansion:

if [[ -v $foo ]]; then
./my-prog
fi

This is documented in man zshmisc, in the CONDITIONAL EXPRESSIONS section.

Bash command starting with colon with another colon before an equals signs

The : command is the null utility:

This utility shall only expand command arguments. It is used when a command is needed, as in the then condition of an if command, but nothing is to be done by the command.

Also Bourne Shell Builtins:

Do nothing beyond expanding arguments and performing redirections. The return status is zero.

The ${foo:=bar} syntax is a special Parameter Expansion:

${parameter:=[word]}

Assign Default Values. If parameter is unset or null, the expansion of word (or an empty string if word is omitted) shall be assigned to parameter. In all cases, the final value of parameter shall be substituted. Only variables, not positional parameters or special parameters, can be assigned in this way.

Bash Reference manual entry:

${parameter:=word}

If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional parameters and special parameters may not be assigned to in this way.

So the command line in your question:

: ${foo:=bar}; export foo

Is two commands:

  1. : ${foo:=bar}
  2. export foo

The first of which expands the variable foo and if it is empty or unset assigns it the value bar.

The second of which then exports the foo variable for sub-shells and other processes.

What is the purpose of the : (colon) GNU Bash builtin?

Historically, Bourne shells didn't have true and false as built-in commands. true was instead simply aliased to :, and false to something like let 0.

: is slightly better than true for portability to ancient Bourne-derived shells. As a simple example, consider having neither the ! pipeline operator nor the || list operator (as was the case for some ancient Bourne shells). This leaves the else clause of the if statement as the only means for branching based on exit status:

if command; then :; else ...; fi

Since if requires a non-empty then clause and comments don't count as non-empty, : serves as a no-op.

Nowadays (that is: in a modern context) you can usually use either : or true. Both are specified by POSIX, and some find true easier to read. However there is one interesting difference: : is a so-called POSIX special built-in, whereas true is a regular built-in.

  • Special built-ins are required to be built into the shell; Regular built-ins are only "typically" built in, but it isn't strictly guaranteed. There usually shouldn't be a regular program named : with the function of true in PATH of most systems.

  • Probably the most crucial difference is that with special built-ins, any variable set by the built-in - even in the environment during simple command evaluation - persists after the command completes, as demonstrated here using ksh93:

    $ unset x; ( x=hi :; echo "$x" )
    hi
    $ ( x=hi true; echo "$x" )

    $

    Note that Zsh ignores this requirement, as does GNU Bash except when operating in POSIX compatibility mode, but all other major "POSIX sh derived" shells observe this including dash, ksh93, and mksh.

  • Another difference is that regular built-ins must be compatible with exec - demonstrated here using Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX also explicitly notes that : may be faster than true, though this is of course an implementation-specific detail.

zsh - how to reference a variable that was dynamically named after multiple other variables?

man zshexpn provides a list of 25(!) rules that govern how expansions are processed. The problem here is that ${key1}_$key2 isn't joined into a single word until step 23, while (P) is applied much earlier. You need a nested expansion to produce a single word upon which (P) can be applied. To do that, you can use the :- operator, which can omit a parameter name, expanding instead to whatever default value you provide.

% print ${:-${key1}_$key2}
a_b

Since nested substitutions are step 1 of the process, the above expression can fill in for the name expected by (P) in step 4.

% print ${(P)${:-${key1}_$key2}}
c


Related Topics



Leave a reply



Submit