I'm trying to set automatically the verbose and xtrace options for shells which are reading their commands from a standard input which is not connected to a terminal (that test is a proxy for being executed by the at(1) and batch(1) commands, if there is a better one it would be acceptable as well).

I'd have though that

if [[ -o SHIN_STDIN ]] && ! test -t 0 ; then
    PS4="> "
    setopt verbose xtrace

in my .zshenv would do the trick but that's not the case although I've checked that the .zshenv is indeed correctly executed and that when launching [[ -o SHIN_STDIN ]] && !test -t 0 && echo OK with batch I get the expected email.

What am I missing?

Note tcsh consider such shells as interactive (it set prompt to a non null value). zsh does not (testing with -o interactive, .zprofile, .zshrc are not read). What I find strange is that -o SHIN_STDIN change of value between .zshenv and the execution of commands registered with batch.

  • My atd implementation interprets the supplied code with sh not zsh. Is yours interpreting them with the login shell of the user instead (or with $SHELL) ? Or is sh on your system actually zsh? Jul 13, 2020 at 12:03
  • It is using $SHELL. From man: "The value of the SHELL environment variable at the time of at invocation will determine which shell is used to execute the at job commands. If SHELL is unset when at is invoked, the user's login shell will be used; otherwise, if SHELL is set when at is invoked, it must contain the path of a shell interpreter executable that will be used to run the commands at the specified time." I'm on an old linux, RHEL 6.5, but that or the user login shell (difficult to make a difference for me now) is what I'm used since my SunOS days, 25 years ago. Jul 13, 2020 at 12:16

1 Answer 1


sh_in_stdin is not on whilst interpreting ~/.zshenv, or when interpreting any sourced file (in those cases, the shell code is not coming from stdin). You can verify it by adding echo $options[shinstdin] to your ~/.zshenv, or running:

$ echo 'echo $options[shinstdin]; source =(<<<"echo \$options[shinstdin]")' | zsh

You could instead check that $PPID is running atd:

if [[ $(ps -o comm= -p $PPID) = atd ]]; then...

Personally, I don't like the idea of doing it unconditionally for every zsh invocation run from atd.

You could always define a debug-at command that does:

debug-at() {
    echo 'PS4="> "; set -o verbose -o xtrace'
  } | at "$@"

And use that instead of at when you do want an at/batch job to run with debugging on.

Beware that not all systems use $SHELL to interpret the supplied code. Debian's at doesn't. POSIX leaves it implementation-defined. The above approach would work regardless of what shell is used (as long as it's Korn/POSIX-like).

  • I found out that [[ -z ${ZSH_EXECUTION_STRING+set} && -z ${ZSH_SCRIPT+set} ]] has the desired effect in .zshenv. Now I've three solutions, I'm not sure yet which I'll use. Perhaps an at function sending to the at executable with command. Jul 13, 2020 at 15:23
  • @AProgrammer, ! (( $+ZSH_EXECUTION_STRING || $+ZSH_SCRIPT )) or ! [[ -v ZSH_EXECUTION_STRING || -v ZSH_SCRIPT ]] would be more zsh'y. Jul 13, 2020 at 15:28
  • Thanks. I'd need a good reference of best practices for shell programming. I've collected some tips along the years, but I do it rarely enough that I've probably forgot half of them, and in the other half I wonder which part is sane, and which is simply cargo cult programming. And then there is the fact that for most scripts I try to be quite portable, but my interactive environment is zsh. Jul 13, 2020 at 15:38
  • Yes, the portable one would be [ -z "${ZSH_EXECUTION_STRING+set}" ] && [ -z "${ZSH_SCRIPT+set}" ], but portability is not relevant here as that's only meant for zsh. Among them, the choice is mostly a matter of taste. Jul 13, 2020 at 15:41

You must log in to answer this question.

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