Tip of the hat to @gniourf_gniourf and @chepner for their help.
tl;dr
To be safe, double-quote: it’ll work in all cases, across all POSIX-like shells.
If you want to add a ~
-based path, selectively leave the ~/
unquoted to ensure that ~
is expanded; e.g.: export PATH=~/"bin:$PATH"
.
See below for the rules of ~
expansion in variable assignments.
Alternatively, simply use $HOME
inside a single, double-quoted string:
export PATH="$HOME/bin:$PATH"
NOTE: The following applies to bash
, ksh
, and zsh
, but NOT to (mostly) strictly POSIX compliant shells such as dash
; thus, when you target /bin/sh
, you MUST double-quote the RHS of export
.[1]
- Double-quotes are optional, ONLY IF the literal part of your RHS (the value to assign) contains neither whitespace nor other shell metacharacters.
- Whether the values of the variables referenced contain whitespace/metacharacters or not does not matter – see below.
- Again: It does matter with
sh
, whenexport
is used, so always double-quote there.
- Again: It does matter with
The reason you can get away without double-quoting in this case is that variable-assignment statements in POSIX-like shells interpret their RHS differently than arguments passed to commands, as described in section 2.9.1 of the POSIX spec:
Specifically, even though initial word-splitting is performed, it is only applied to the unexpanded (raw) RHS (that’s why you do need quoting with whitespace/metacharacters in literals), and not to its results.
This only applies to genuine assignment statements of the form
<name>=<value>
in all POSIX-like shells, i.e., if there is no command name before the variable name; note that that includes assignments prepended to a command to define ad-hoc environment variables for it, e.g.,foo=$bar cmd ...
.Assignments in the context of other commands should always be double-quoted, to be safe:
With
sh
(in a (mostly) strictly POSIX-compliant shell such asdash
) an assignment withexport
is treated as a regular command, and thefoo=$bar
part is treated as the 1st argument to theexport
builtin and therefore treated as usual (subject to word-splitting of the result, too).
(POSIX doesn’t specify any other commands involving (explicit) variable-assignment;declare
,typeset
, andlocal
are nonstandard extensions).bash
,ksh
,zsh
, in an understandable deviation from POSIX, extend the assignment logic toexport foo=$bar
andtypeset/declare/local foo=$bar
as well. In other words: inbash
,ksh
,zsh
,export/typeset/declare/local
commands are treated like assignments, so that quoting isn’t strictly necessary.- Perhaps surprisingly,
dash
, which also chose to implement the non-POSIXlocal
builtin[2]
, does NOT extend assignment logic to it; it is consistent with itsexport
behavior, however.
- Perhaps surprisingly,
Assignments passed to
env
(e.g.,env foo=$bar cmd ...
) are also subject to expansion as a command argument and therefore need double-quoting – except inzsh
.- That
env
acts differently fromexport
inksh
andbash
in that regard is due to the fact thatenv
is an external utility, whereasexport
is a shell builtin.
(zsh
‘s behavior fundamentally differs from that of the other shells when it comes to unquoted variable references).
- That
Tilde (
~
) expansion happens as follows in genuine assignment statements:- In addition to the
~
needing to be unquoted, as usual, it is also only applied:- If the entire RHS is
~
; e.g.:foo=~ # same as: foo="$HOME"
- Otherwise: only if both of the following conditions are met:
- if
~
starts the string or is preceded by an unquoted:
- if
~
is followed by an unquoted/
. - e.g.,
foo=~/bin # same as foo="$HOME/bin"
foo=$foo:~/bin # same as foo="$foo:$HOME/bin"
- if
- If the entire RHS is
- In addition to the
Example
This example demonstrates that in bash
, ksh
, and zsh
you can get away without double-quoting, even when using export
, but I do not recommend it.
#!/usr/bin/env bash
# or ksh or zsh - but NOT /bin/sh!
# Create env. variable with whitespace and other shell metacharacters
export FOO="b:c &|<> d"
# Extend the value - the double quotes here are optional, but ONLY
# because the literal part, 'a:`, contains no whitespace or other shell metacharacters.
# To be safe, DO double-quote the RHS.
export FOO=a:$foo # OK - $FOO now contains 'a:b:c &|<> d'
[1] As @gniourf_gniourf points out: Use of export
to modify the value of PATH
is optional, because once a variable is marked as exported, you can use a regular assignment (PATH=...
) to change its value.
That said, you may still choose to use export
, so as to make it explicit that the variable being modified is exported.
[2] @gniourf_gniourf states that a future version of the POSIX standard may introduce the local
builtin.