I mean I want to use unset
that is not a shell function itself. If I could do that, I could make sure that command
is pure by running
#!/bin/sh
{ \unset -f unalias command [; \unalias unset command [ } 2>/dev/null;
# make zsh find *builtins* with `command` too:
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on
If I am using Debian Almquist shell (dash), I think I can rely that \unset
is pure. At least I could not define a shell function named unset
in dash
. Whereas in bash
or in zsh
I could define unset() { echo fake unset; }
, and thereafter I am unable to unset the function: \unset -f unset
outputs "fake unset".
Relating to this, in a bash
script, one can export a function by export -f <function name>
so that it can be used in bash
scripts called by the script. However, the same does not work in dash
scripts. I wonder, if I have to worry about a command being defined as a shell function outside a script file I am writing, if I am using dash
? How about other POSIX compatible shells?
This is what I know what can be done...
funnily enough, you already said the builtin name --
command
this doesn't help if you're in a hostile environment where someone has created a
command() { :; }
function. but if you're in a hostile environment, you've already lost ;).when it comes to exporting functions into the environment, that's a bash-specific extension and you should not really rely on that. POSIX shells (like dash) do not support that by design.
Note: The following applies to all major POSIX-compatible shells, except where noted otherwise:
bash
,dash
,ksh
, andzsh
. (dash
, the Debian Almquist Shell, is the default shell (sh
) on Debian-based Linux distros such as Ubuntu).unset
having its original meaning - a builtin that can undefine shell functions with its-f
option - is the key to ensuring that any other shell keyword, command, or builtin has its original meaning.unset
, you can ensure an unmodifiedshopt
and/orcommand
, and together they can be used to bypass or undefine any aliases or shell functions that may shadow shell keywords, builtins, and external utilities.command
can be used to bypass them, including those that may have been defined outside of your code, through the environment;exporting functions, as only
bash
supports, is only one of these mechanisms; different shells have different ones and may support several - see below.Only
dash
,ksh
, andbash
when in POSIX compatibility mode guarantee thatunset
wasn't redefined:dash
andksh
are safe, because they don't allow defining a function namedunset
, as you've discovered, and any alias form can be bypassed by invoking as\unset
.bash
, when in POSIX compatibility mode, allows you to define a function namedunset
, but ignores it when you invokeunset
, and always executes the builtin, as you've later discovered.unset
function is defined.Sadly, as far as I know, in
zsh
- and also inbash
's default mode - there is no way to guarantee thatunset
itself hasn't been redefined, and there may be other POSIX-like shells that behave similarly.\unset
(quoting any part of the name) would bypass an alias redefinition, but not a function redefinition - and to undo that you would need the originalunset
itself: catch 22.Thus, with no control over the execution environment, you cannot write shell scripts that are fully immune to tampering, unless you know that your code will be executed by
dash
,ksh
, orbash
(with the workaround in place).If you're willing to assume that
unset
has not been tampered with, the most robust approach is to:Use
\unset -f
to ensure thatunalias
andcommand
are unmodified (not shadowed by a shell function:\unset -f unalias command
)typeset -f
works inbash
,ksh
, andzsh
, butdash
appears to have no mechanism at all), so that undefining all functions is not always possible.Use
\unalias -a
to remove all aliases.Then invoke everything with
command [-p]
, except for functions you have defined. When invoking external utilities, use explicit paths when possible, and/or, in the case of standard utilities, usecommand -p
, which uses a minimal$PATH
definition restricted to standard locations (runcommand -p getconf PATH
to see that definition).Additional info:
Per POSIX, quoting any part of a command name (e.g.,
\unset
) bypasses any alias form or keyword form (reserved word in POSIX andzsh
parlance) by that name - but not shell functions.Per POSIX,
unalias -a
undefines all aliases. There is no equivalent, POSIX-compliant command for undefining all functions.zsh
versions do not support-a
; as of at leastv5.0.8
, however, they do.Builtin
command
can be used to bypass keywords, aliases, functions inbash
,dash
, andksh
- in other words:command
only executes builtins and external utilities. By contrast,zsh
by default also bypasses builtins; to makezsh
execute builtins too, useoptions[POSIX_BUILTINS]=on
.The following can be used to execute external utilities named
<name>
only, across all shells:"$(command which <name>)" ...
Note that while
which
is not a POSIX utility, it is widely available on modern Unix-like platforms.Precedence of command forms:
bash
,zsh
: alias > shell keyword > shell function > builtin > external utilityksh
,dash
: shell keyword > alias > shell function > builtin > external utilitybash
andzsh
an alias can override a shell keyword, while inksh
anddash
it cannot.bash
,ksh
, andzsh
- but notdash
- all allow a nonstandard function signature,function <name> { ...
, as an alternative to the POSIX-compliant<name>() { ...
form.function
syntax is the prerequisite for:<name>
isn't itself subject to alias expansion before the function is defined.<name>
that is also a shell keyword;note that such a function can only be invoked in quoted form; e.g.,
\while
.ksh
, usingfunction
syntax additionally implies thattypeset
statements create local variables.)dash
,ksh
, andbash
when in POSIX mode additionally prevent naming functions for special builtins (e.g.,unset
,break
,set
,shift
); the list of POSIX-defined special builtins can be found here; bothdash
andksh
add a few more that cannot be redefined (e.g.,local
indash
;typeset
andunalias
inksh
), but both shells have additional, non-special builtins that can be redefined (e.g.,type
).Note that in the case of
ksh
the above rules apply irrespective of whetherfunction
syntax is used or not.Potential sources of environment shell functions in scope for your code:
Note: The simplest way to guard against these is to use the (unmodified)
command
builtin (inzsh
withoptions[POSIX_BUILTINS]=on
, to prevent bypassing of builtins as well) whenever you want to call a builtin or external utility.POSIX mandates that a script specified by its absolute path in environment variable
ENV
be sourced for interactive shells (with some restrictions - see the spec);ksh
anddash
always honor that, whereasbash
only does so when invoked assh
or, in v4.2+, with--posix
; by contrast,zsh
never honors this variable.sh -i
to force an interactive instance.bash
has 2 mechanisms:export -f
ordeclare -fx
(the other shells only support exporting variables)BASH_ENV
.ksh
supports auto-loading of functions via the optionalFPATH
environment variable: files containing function definitions located in any directory specified inFPATH
are implicitly and automatically loaded.zsh
supportsFPATH
too, but auto-loading functions requires an explicitautoload <name>
statement, so unless you specifically ask for a function by a given name to auto-load, no functions will be added to your shell.)zsh
supports sourcing scripts for anyzsh
instance (whether interactive or not) via its/etc/zshenv
and~/.zhsenv
initialization files.(
dash
appears not to support any mechanism for defining functions via the environment.)Workaround for
bash
: Ensure thatunset
has its original meaning:This workaround is only safe if you know that
bash
will be executing your script, which, unfortunately, cannot itself be guaranteed.Also, because it modifies the shell environment (removal of aliases and functions), it is not suitable for scripts that are designed to be sourced.
As stated, it's usually not desirable to run your code in Bash's POSIX compatibility mode, but you can temporarily activate it in order to ensure that
unset
is not shadowed by a function:Test command:
Assume that the above code was saved to file
script
in the current dir.The following command simulates a tampered-with environment where
unset
is shadowed by both an alias and a function, and filescript
is sourced, causing it to see the function and, when sourced interactively, to expand the alias too:type unset
outputtingunset is a shell builtin
is proof that both the function and the alias shadowing the builtinunset
were deactivated.