this works as expected in bash:

> t="ls -l"
> $t #== ls -l
> "$t" #== "ls -l"
ls -l: command not found

But in zsh I got this:

> t="ls -l"
> $t #== "ls -l"
ls -l: command not found

How can I force the shell to parse the variable value again like bash does?

  • 2
    Possible duplicate of How can we run a command stored in a variable? – muru Jul 5 '19 at 9:57
  • 1
    @muru, the question you linked is related to bash. while it's close enough, it's still different :) – rush Jul 5 '19 at 10:03
  • @rush doesn't matter, the only sane way to run commands is using arrays. – muru Jul 5 '19 at 10:34

If you want a variable that expands to more than one argument use an array:

var=(ls -l)

But to store code, the most obvious storage type is a function:

myfunction() ls -l


myfunction() ls -l "$@"

for that function to take extra arguments to be passed to ls.

The fact that bash like most other Bourne-like shells splits unquoted variables upon expansion is IMO a bug. See the kind of problems it leads to. But if you want that behaviour, you can set the shwordsplit option. You could also add the globsubst option to restore another bug found in bash and other Bourne-like shells where variable expansion is also subject to globbing (aka pathname expansion). Or do the full shebang with emulate sh or emulate ksh (but lose a few more zsh features).

Without having to go there, you can also tell zsh to explicitly split a variable:

var='ls -l'
$=var # split on $IFS like the $var of bash/sh
${(s: :)var} # split on spaces only regardless of the value of $IFS
echo $~var # do pathname expansion like the $var of bash/sh
var='ls -l *.txt'
$=~var # do both word splitting and filename generation
  • I don't consider word splitting it a bug at all, since bash is built on that concept (that's why IFS is there and in that form). But it doesn't matter, I just wanted to understand why zsh makes no difference between quoted and unquoted. Thanks for the thorough explanation! – estani Jul 7 '19 at 12:00
  • Reading your post for a second time I realize you mentioned "$@" which is funny, cause that's there because of word splitting. Without code splitting theres no difference between $*, "$*",$@, "$@",$1, "$1", right? Everything is just one large parameter (but perhaps zsh behaves differently under some circumstances like function calls... not sure... – estani Jul 7 '19 at 12:07
  • 1
    @estani, zsh makes a difference between quoted and unquoted. For a scalar variable, the difference is when the variable is empty in which case $var expands to no argument at all while "$var" expands to one empty argument. For arrays, "$var" is like "${var[*]}" in ksh/bash (elements joined with the first character of $IFS), while $var is all the non-empty elements as separate arguments (same as ${var[*]} after a set -o noglob; IFS= in ksh/bash). For all the elements (including empty ones) as separate arguments you still need "${var[@]}" like in ksh/bash... – Stéphane Chazelas Jul 7 '19 at 13:06
  • ... though you can shorten it to "$var[@]" or can also use the @ parameter expansion flag ("${(@)var}"). – Stéphane Chazelas Jul 7 '19 at 13:07
  • 2
    $*/"$*"/$@/"$@"/$1`` are short forms of $argv[*], "$argv[*]", $argv[@], "$argv[@]", $argv[1] so are all different. The main difference with ksh/bash is again that word splitting and globbing are not done on top of that when unquoted (which for arrays makes even less sense). – Stéphane Chazelas Jul 7 '19 at 13:10

This item is covered in zsh faq.

Long story short. There are a few ways to fix the behavior:

  1. Set setopt shwordsplit:
setopt shwordsplit
t="ls -l"
  1. Use eval (which better to avoid due to possible security issues):
t="ls -l"
eval $t
  • thanks, I didn't know what to look for (didn't realize the term was word splitting) – estani Jul 7 '19 at 12:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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