Running zsh Scripts Through Management Tools

Management tools may run your scripts through bash or sh, resulting in errors when using features of zsh that are not found in those other shells.

One way around this is to install your script locally to a device, and then use the management tool to execute that file. This requires you to either clean up after yourself, or maintain versioning on each device. Not ideal in many situations.

This trick should allow you to be sure your script is interpreted by the specific shell of your choosing, regardless of what shell your management tool decides to use to process your script.

Pipe a HereDoc to /bin/zsh

There appear to be a few different ways to get to this same place. This Unix Stack Exchange post covers a bunch. The method below works for me with Mosyle, and should work with other management tools. If you succeed or fail with this method using another tool, please let me know.

#!/bin/sh

{
cat <<'EOF'
#!/bin/zsh

# Paste your entire script here.
# Anything between the two lines containing EOF
#    will be executed as a zsh script.

echo "I am a zsh script"

EOF
} | /bin/zsh

Using << creates a “heredoc” and results in everything between the string EOF will be run as an entire script, which is sent to the cat command which prints the text, and then the | pushes that text into the shell of your choosing. In my example, I’m | piping output to /bin/zsh .

Use Single Quotes On Your Delimiter

A key detail here, is that you must use single quotes when creating the delimiter for your heredoc . Using cat <<'EOF' prevents variables in the “zsh block” from being expanded in the primary shell (by the management tool.) This StackOverflow post gives some additional details.

A More Robust Example

zsh has great options for splitting a file path using substitution. This example script flexes that, even if executed using /bin/sh or /bin/bash .

#!/bin/sh
#set -x 

#####################
# Put nothing here  #
#####################

{
cat <<'EOF'
#!/bin/zsh
#############################################
# Put all zsh commands between here and EOF #
#############################################

exampleFilePath="/path/to/filename.txt"

echo "This is exampleFilePath: $exampleFilePath"
echo "Length of File Path: ${#exampleFilePath}"
echo "Length of File Name: ${#exampleFilePath:t}"
echo "File Name: ${exampleFilePath:t}"
echo "File Extension: ${exampleFilePath:e}"
echo "File Name No Extension: ${exampleFilePath:r:t}"

#################################
#   Nothing below this point    #
#################################

EOF
} | /bin/zsh

Even if executed using /bin/sh or /bin/bash this example script will utilize zsh specific features for variable substitution.

% /bin/sh zshExample.sh 
This is exampleFilePath: /path/to/filename.txt
Length of File Path: 21
Length of File Name: 12
File Name: filename.txt
File Extension: txt
File Name No Extension: filename

Limitations

There are definite limitations to this. For example, I was unable to determine a way to pass arguments to the zsh script block. An alternate method for that could be to write the heredoc out to a temp file and then use chmod +x and then execute the script using /bin/zsh /var/tmp/tempScript.sh --arguments .

There are probably numerous other limitations and gotchas I haven’t considered here, but this trick has worked for me as long as all of the logic and variables are configured within the “zsh block.”

Mosyle Custom Commands

Mosyle CDN variables and other Custom Command features can be used within the “zsh block” without any modifications to the example script.

#!/bin/sh
#set -x 

#####################
# Put nothing here  #
#####################

{
cat <<'EOF'
#!/bin/zsh
#############################################
# Put all zsh commands between here and EOF #
#############################################

echo "Mosyle Variables: %ProductName% %CompanyName% %SerialNumber%

#################################
#   Nothing below this point    #
#################################

EOF
} | /bin/zsh

3 responses to “Running zsh Scripts Through Management Tools”

    • In Mosyle specifically, this command: “`#!/bin/zsh

      exampleFilePath=”/path/to/filename.txt”

      echo “This is exampleFilePath: $exampleFilePath”
      echo “Length of File Path: ${#exampleFilePath}”
      echo “Length of File Name: ${#exampleFilePath:t}”
      echo “File Name: ${exampleFilePath:t}”
      echo “File Extension: ${exampleFilePath:e}”
      echo “File Name No Extension: ${exampleFilePath:r:t}”“`

      Results in this return:
      This is exampleFilePath: /path/to/filename.txt
      Length of File Path: 21
      /bin/bash: line 6: Length of File Name: ${#exampleFilePath:t}: bad substitution
      File Name: /path/to/filename.txt
      File Extension: /path/to/filename.txt
      File Name No Extension:

      The documentation around how this works is lacking quite a bit, but based on that error I can see that Mosyle always chooses to use bash regardless of the shebang you may enter there. This is not the only management tool in which I’ve observed this quirk.

      Liked by 1 person

Leave a reply to bigmacadmin Cancel reply

Design a site like this with WordPress.com
Get started