1

Why is it that zsh seems to understand what to do with some_variable=1 command, but not some-variable=1 command? E.g.: $ fs_screen=1 mpv --fs-screen=${fs_screen} someFile.mp4 works fine but $ fs-screen=1 mpv --fs-screen=${fs-screen} someFile.mp4 results in zsh: command not found: fs-screen=1

I went digging through the zsh documentation and found what I think the issue is. Excerpt from https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html#Precommand-Modifiers:

A simple command may be preceded by a precommand modifier, which will alter how the command is interpreted. These modifiers are shell builtin commands with the exception of nocorrect which is a reserved word.

-
The command is executed with a ‘-’ prepended to its argv[0] string.

However, I can't find any more information about this anywhere. What is its purpose, and is it possible to disable it, so that I can use some-variable rather than some_variable?

2
  • 1
    Maybe this answers your question Commented Oct 5, 2023 at 6:02
  • If you get: cd: string not in pwd: - try running: unalias -- - in your current shell session or remove the alias from the zsh source file Commented Oct 5, 2023 at 6:04

1 Answer 1

7

- is not valid in a zsh variable name and could never be if only because

  • ${var-foo} is a parameter expansion operator from the Bourne shell and POSIX that expands to foo if $var is not set
  • $(( var-1 )) from the Korn shell and POSIX is meant to do a subtraction arithmetic operation¹.

Valid characters in variable names are alnums and underscores, and cannot start with digits.

var=1 in var=1 or </dev/null var=1 cmd is recognised as an assignment because it appears before a command argument and has an unquoted = preceded by a valid unquoted variable name.

In echo var=1, \var=1, var\=, 'v'ar=1, a+b=c, a-b=c, those are not treated as assignments for various reasons (after a command argument, variable name or = quoted or quoted in part, invalid variable names), so they are treated as normal command argument, and the command to run is derived from the first argument

In fs-screen=1, as fs-screen is not a valid shell variable name, that is not recognised as an assignment, so that's taken as a command argument, and as it's the first, that's treated as the command's name. So it will be looked up in the list of builtins, functions, alias names and executable files in $PATH, and if not found you get an error.

You could define a fs-screen=1 function with:

$ 'fs-screen=1'() echo hi
$ fs-screen=1
hi

(à la Bourne) Or:

$ function fs-screen=1 { echo hi; }
$ fs-screen=1
hi

(à la Korn).

Or an alias with:

$ aliases[fs-screen=1]='echo hi'
$ fs-screen=1
hi

You can also define a function or have an executable called var=1, but to invoke it, you'd need to quote either the = or any part of the variable name (or prefix it with things like env or command for executables) to avoid it being treated as assignment:

$ 'var=1'() echo hi
$ var=1
$ v\ar=1
hi

In zsh, assignments can also be:

  • var[index]=value to assign to an array element or a character in a string
  • var[first,start]=value same for a range or elements or characters
  • 1=foo same as argv[1]=foo: contrary to other Bourne-like shells, positional parameters can be treated as variables
  • var=(1 2) is an array or associative array assignment as a whole. Newer versions also have assoc=( [foo]=bar ) as a clunky alternative to assoc=( foo bar ) for compatibility with ksh93/bash.

The next version of zsh after 5.9.x will also have namespaces so you'll be able to do a.x=foo (but not a.b.c=foo; ksh93 already has namespaces and also compound variables and multidimensional arrays, and also allows whitespace inside array indices, so you can do a[1 + 1][4].x[foo].bar=13 (even though POSIX requires that to run the a[1 command)).

Now note that while - is not valid in variable names, all characters and even non-characters with the exception of NUL and = can appear in an environment variable name in most Unix-like systems. It's also possible (though often not easy) to have an environment variable with an empty name.

But those that contain characters other than alnums or underscores, or start with digits or are among the list that zsh won't import from the environment such as IFS and USERNAME cannot be mapped to shell variables:

$ env $'a\nb-c'=123 printenv $'a\nb-c'
123

(that's an environment variable name containing not only - but also a newline character).

Languages like perl or python3 can access it:

$ env $'a\nb-c'=123 perl -le 'print $ENV{"a\nb-c"}'
123
$ env $'a\nb-c'=123 python3 -c 'import os; print(os.getenv("a\nb-c"))'
123

But not zsh nor any other Bourne-like shell, as the way they access environment variables is via a mapping to their own variables.

$ env a-b=1 a_b=2 zsh -c 'echo ${parameters[a-b]-notfound} ${parameters[a_b]-notfound}; printenv a-b'
notfound scalar-export
1

a_b was mapped to the $a_b scalar (not array nor assoc) shell variable and given the export attribute since it came from the environment, but a-b was not mapped. zsh still leaves it in the environment, so it will be passed along to the commands it executes (such as printenv above). Some other shells like mksh remove them from the environment.


¹ you might argue that one could use ${var-name} there if wasn't for the previous point, like you can use $(( $! - 1 )) or $(( ${!} - 1 )) for the $! parameter where ! is otherwise also an arithmetic operator (same for $?, $*, $# and of course $1, $2...) but those are not variables. The point of variables is that you can assign values to them, and while you can do $(( $var )) in place of $(( var )), you can't do $(( $var = 1+1 )) in place of $(( var = 1+1 )) as those $var are expanded before the arithmetic expression is evaluated.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.