Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the same command in a Z shell script interpreted differently than when directly run in a Z shell shell?

I have written a helper script (Z shell (executable zsh) dialect) that installs Python's Poetry package manager. A weird problem occurs where the same shell command is interpreted differently, when run (a) in a shell script vs (b) directly in Z shell. An excerpt of the script is this:

#!/bin/zsh
set -x
INSTALL_SPECIFIC_VERSION="--version 1.6.1"
# Why does this not work?
echo "curl -sSL URL | python3 - $INSTALL_SPECIFIC_VERSION"
# will print:
# curl -sSL URL | python3 - --version 1.6.1
curl -sSL URL | python3 - "$INSTALL_SPECIFIC_VERSION"
# The fix I use which works but I don't understand why, is:
# curl -sSL URL | python3 - $(printf "%s" "$INSTALL_SPECIFIC_VERSION")
set +x

Here is the result when the shell script is executed:

➜  /usr/bin/zsh test_script.zsh
+test_script.zsh:3> INSTALL_SPECIFIC_VERSION='--version 1.6.1'
+test_script.zsh:5> echo 'curl -sSL URL | python3 - --version 1.6.1'
curl -sSL URL | python3 - --version 1.6.1
+test_script.zsh:8> curl -sSL URL
+test_script.zsh:8> python3 - '--version 1.6.1'
usage: - [-h] [-p] [--version VERSION] [-f] [-y] [--uninstall] [--path PATH] [--git GIT]
-: error: unrecognized arguments: --version 1.6.1
+test_script.zsh:9> set +x

The argument --version 1.6.1 can not be interpreted by remotely called shell script.

When I call the printed (echo) cmd in Z shell it works fine. See:

➜  /usr/bin/zsh -c "curl -sSL URL | python3 - --version 1.6.1"
Retrieving Poetry metadata

The latest version (1.6.1) is already installed.

My current fix in the shell file is this:

# The fix I use which works but I don't understand why, is:
curl -sSL URL | python3 - $(printf "%s" "$INSTALL_SPECIFIC_VERSION")

How come the variable's value is passed/interpreted differently. What am I missing here?

Z shell version: 5.8.1 (x86_64-ubuntu-linux-gnu)

like image 221
antonio Avatar asked Oct 20 '25 13:10

antonio


1 Answers

You cannot (sensibly nor sanely) store multiple words (arguments, commends, etc) in a single variable. What you can do however, is to expand a parameter with an alternative value containing a single word:

${parameter:+word} Use Alternative Value. If parameter is unset or null, null shall be substituted; otherwise, the expansion of word shall be substituted.

In your case:

#!/bin/sh -x
# you don't need zsh for this, plain sh works just fine
INSTALL_SPECIFIC_VERSION="1.6.1"
curl -sSL URL | python3 - ${INSTALL_SPECIFIC_VERSION:+--version "$INSTALL_SPECIFIC_VERSION"}

This will expand to nothing if the parameter is not set to a value and expand to two words --version and 1.6.1 if the parameter contains a value.

Alternatively, use plain old if:

#!/bin/sh -x
INSTALL_SPECIFIC_VERSION="1.6.1"
if test "$INSTALL_SPECIFIC_VERSION"; then
  curl -sSL URL | python3 - --version "$INSTALL_SPECIFIC_VERSION"
else
  curl -sSL URL | python3 -
fi

Here's why you always want to quote your parameter expansions: Security implications of forgetting to quote a variable in bash/POSIX shells. Running arbitrary commands from a remote server is also security-critical and should be avoided if possible (but sometimes there's no way around it).

Resources:

  • How can I store a command in a variable in a shell script?
like image 198
knittl Avatar answered Oct 22 '25 04:10

knittl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!