7

I would like to source (i.e. not call) a script from any shell (bash/csh are the primary targets, but fish, zsh, ksh, and rc would also be interesting).

I would like if the script can be a single file - i.e. not a file for each shell dialect.

Can I do that?

I am thinking of something similar to:

  if shell is bash then
    # bash code here
  else
    if shell is csh then
      # csh code here
    else
      if shell is xxxsh then
        # xxxsh code here
      fi
    endif
  fi

So I can do:

csh% source my_script
bash$ . my_script

The trouble is - of course that if is not the same in each dialect, so I somehow need to use syntax that is valid for every shell.

Edit

Detecting the shell is the first step of which determine shell in script during runtime is doing a great job of.

An equally important step is how the code for the different sections for shells should be quoted to not confuse other shells. Think: How can you in the section for bash have a <<here_document containing all characters in all combinations legal in bash but illegal in any other shell without this confusing the other shells. This is not covered by any of the answers/linked answers.

10

3 Answers 3

2

Instead of writing fragments of code for every shell, you should just write portable code, which can be interpreted by most shells. You should look at the POSIX Shell Command Language. This is a standard how a shell (which honors POSIX) should interpret code.

Many shells, like bash, can be configured to behave like a POSIX shell. Every shell has his won features and specific notations. Aviod them completely in portable scripts.

2
  • 3
    The OP specifically mentioned csh which, among its many other failings, is most certainly not POSIX-compliant so this isn't likely to help them much.
    – terdon
    Commented Oct 21, 2015 at 23:18
  • 1
    anyone writing csh scripts should be advised not to and referred to perl.com/doc/FMTEYEWTK/versus/csh.whynot and shlomifish.org/open-source/anti/csh Way back in the dark ages of the 1980s, csh made a pretty decent interactive shell because it had neat features like history recall and editing. Other, better, shells have long since gained and surpassed those features without the drawbacks of csh.
    – cas
    Commented Oct 22, 2015 at 0:43
2

For csh-like versus Bourne-like, you could do:

start=:#||:<<"goto end="

echo "(t)csh code here"
if { bindkey >& /dev/null } then
  echo tcsh
endif

goto end=

echo Bourne-like code here
if [ -n "$BASH_VERSION" ]; then
  echo bash
fi

end=:

Explained:

  • start=: would be treated as a label declaration in csh, and variable assignment in sh, so two harmless operations.
  • In start=:#, that # is treated as a comment leader csh, but not in sh as it's not a separate token. So what's after that is commented out for csh but not for sh.
  • a variable assignment is sh has a success exit status (as long as the assignments doesn't involve command substitutions), so the command right of the || operator won't be run.
  • That :<<"goto end="...goto end command is not run but is parsed, and ignored (the fact that "goto end" is quoted prevents various expansions inside the here document). A note however: in the Bourne shell (and only in the Bourne shell), a temporary file is still created.
  • So the whole section until goto end= is ignored by Bourne-like shell and run by csh. goto end= causes csh to ignore the part between that and end=: (a harmless variable assignment in Bourne-like shells).

The more shell support you add, the trickier it gets. For fish especially, it's quite tricky as it checks the syntax of the whole script event the parts it doesn't run.

See also:

-1

I assume that the reason you want to do that is for portability. The above answers give you a clue. If you assume that say, the Bourne shell (sh) is the least common denominator, and available 'everywhere' (Linux, Solaris, Unix, AIX etc) then you can put a #!/bin/sh at the start of your script, and write all your script using only the features of sh. (I think that in Linux, sh is just an alias for bash, because bash is a superset of the Bourne shell, but no matter, it should still work.)

A slight refinement is to assume that sh is always there, but you might need the extra power and convenience of some of the more advanced shells. so you could write a version of the script for each of the shells you support, e.g. script.bash, script.zsh, script.ksh, script.csh etc. each one beginning with its own shebang line, and then in the script.sh file you would say something like:

#!/bin/sh
if [[ -e /bin/ksh ]] ; then 
 ./script.ksh
 exit
elif [[ -e /bin/csh ]] ; then
 ./script.csh
 exit
fi

etc.

A further suggestion is to study the source code of the auto-tools tool chain. Those things that run when you do a ./configure ; make ; make install. The GNU folks have done a lot of clever things to ensure that their scripts run in all environments.

1
  • The OP specifically mentioned they want to avoid having a separate script per shell. Also, your solution is checking for the existence of the shells on the system, not for what interpreter is currently reading the script, so that won't help either. Finally, note that, these days, sh is often a link to dash, but even when it is indeed a link to bash, calling bash as sh causes it to run in POSIX mode and behave more like traditional sh than bash.
    – terdon
    Commented Oct 21, 2015 at 23:16

You must log in to answer this question.

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