I want to add a function to precmd_functions at ZSH login and avoid duplication. Since /etc/zprofile source the /etc/profile, which then source all the *.sh script under /etc/profile.d/, my solution is to add init scripts to /etc/profile.d. To keep compatible with bash, the auto sourced script new_script.sh is like:

# zsh user
if [ -n "$ZSH_VERSION" ]; then
    source /etc/profile.d/new_script.zsh
# bash user
elif [ -n "$BASH_VERSION" ]; then
    source /etc/profile.d/new_script.bash

To here it all green, but then the new_script.zsh made a strange behaviour. It has contents like:

    if (( $precmd_functions[(I)audit_hook] )); then

When I manually source it after login in using zsh, it run without any problem. But when automatically sourced at login init process, it reported bad output format specification in the if (( $precmd_functions... line.

So why only login init report this error while manually source the script not?

  • 1
    What's the verbatim error message that you get, and could you try starting a shell with zsh -l -x to maybe see what's going on around when the error occurs?
    – Kusalananda
    Commented Dec 29, 2020 at 12:12
  • zsh -l -x shows these error related info: +/etc/profile.d/z03_log_command.sh:3> source /etc/profile.d/new_script.zsh /etc/profile.d/new_script.zsh:6: bad output format specification +/etc/profile.d/new_script.zsh:6> (( [(I)audit_hook] )) Commented Dec 30, 2020 at 1:54

1 Answer 1


Files under /etc/profile.d are expected to be written in sh syntax, so presumably the code in /etc/zprofile that sources /etc/profile.d/* arranges to do it in sh or ash emulation mode, perhaps something like emulate ksh -c '. /etc/profile'.

One of the differences between sh and zsh syntax is that $foo[bar] is parsed as an array variable dereference in zsh, but in sh (and compatible shells such as bash and zsh) it's a string variable followed by a single-character glob (so touch somefilename1 somefilename2 somefilename3; var=somefilename; echo $var[12] prints somefilename1 somefilename2 in sh/bash/ksh, but e in zsh). When zsh is in sh or ksh emulation mode, the zsh_arrays option is enabled, so it does not parse $precmd_functions[(I)audit_hook] as an array access. precmd_functions is empty at this point (if it wasn't, you'd very likely get some different error), so the arithmetic expression ends up being [(I)audit_hook]. A bracketed part in a zsh arithmetic expression is an output format specification, instructing the output to be formatted in a different base (and a few other possibilities, which is why it isn't just an output base specification). When zsh sees the opening bracket, it prepares itself to parse an output format specification, which fails.

If you write zsh-specific code in /etc/profile.d and want to use zsh syntax, tell zsh explicitly to use zsh syntax:

if [ -n "$ZSH_VERSION" ]; then
    emulate zsh -c 'source /etc/profile.d/new_script.zsh'

or put all your code in new_script.zsh in a function and put emulate -L zsh at the top of the function. (Don't put emulate -L … outside a function: it isn't local to a sourced script.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .