Should I use quotes in environment path names?
Asked Answered
G

4

60

I'm in the process of cleaning up all my config files in an attempt to make them as readable as possible. I've been looking for a style guide on the use of quotes while exporting paths in, for example, a ~/.bashrc file:

export PATH="/users/me/path:$PATH"

vs

export PATH=/users/me/path:$PATH

The Google shell style guide suggests avoiding quotes for path names. In contrast, a lot of the popular dotfiles repos (such as Zach Holman's here) use quotes. Are there any situations when it is an advantage to use quotes in the path?

Geoid answered 24/10, 2015 at 13:0 Comment(3)
I think you should use quotes when a directory name contains white spaces.Linter
In this case, you must use quotes. Without quotes, the shell will perform word splitting and pathname expansion (and you certainly don't want that). Note though that PATH is certainly already exported, so you don't need to export it again. PATH=/users/me/path:$PATH (this time without quotes) is enough.Muoimuon
Google also has a requirement that file names in general, and directories in paths on particular, only contain characters from the portable filename character set. That means only (Latin) alphabet, digits, underscore, dash, dot. When that rule applies, it is safe to leave off the quotes. It's only when people include bizarre characters in the directory name that quoting becomes a necessity.Combined
C
53

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, when export is used, so always double-quote there.

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 as dash) an assignment with export is treated as a regular command, and the foo=$bar part is treated as the 1st argument to the export 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, and local are nonstandard extensions).

    • bash, ksh, zsh, in an understandable deviation from POSIX, extend the assignment logic to export foo=$bar and typeset/declare/local foo=$bar as well. In other words: in bash, 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-POSIX local builtin[2] , does NOT extend assignment logic to it; it is consistent with its export behavior, however.
    • 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 in zsh.

      • That env acts differently from export in ksh and bash in that regard is due to the fact that env is an external utility, whereas export is a shell builtin.
        (zsh's behavior fundamentally differs from that of the other shells when it comes to unquoted variable references).
  • 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"

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.

Catheycathi answered 24/10, 2015 at 13:43 Comment(5)
+1 for the reference to POSIX section 2.9.1 and an (indirect) explanation of why FOO="${SOMEPATH}/bar.txt" ; cat ${FOO} doesn't need to be (double) quoted even if ${SOMEPATH} happens to contain spaces.Alecto
Thanks, @ChristopherSchultz - you do need double quotes around ${FOO}, though :)Catheycathi
Hmm... seems I'm out of luck when trying to build an env var which contains an optional parameter to a command, e.g. [ predicate ] && OPTARG="-f ${MYPATH}/file" ; command ${OPTARG}. Could you add an example to your answer which explains and/or handles this particular case?Alecto
@ChristopherSchultz: This is really a separate question, so here's just a quick pointer: In bash, use an array to store your arguments in, then pass that array in double quotes, as demonstrated here. The only portable solution is the - risky - use of eval.Catheycathi
Another thing is, if your key has a hashtag # and you don't quote, everything after the # will be interpreted as a comment and ignored!Hitlerism
N
16

I used these answers above when setting environment path names in a docker .env file, and got bit. I'm putting this here for anyone else looking for how to define environment variables for docker.

Docker compose reads environment variables from an .env file that exists in the same folder that docker compose is run as stated here https://docs.docker.com/compose/env-file.

However instead of wrapping the value in quotes, docker compose needs the environment variable defined without quotes, unless the quotes are part of the value. Again, as stated in the url above

There is no special handling of quotation marks (i.e. they will be part of the VAL, you have been warned ;)

I was trying to set NODE_PATH=./src for absolute paths to work in a react app being deployed by docker, but had written it as NODE_PATH="./src". This warning pulled me out of 4 hour rabbit hole.

Neill answered 23/1, 2018 at 21:35 Comment(2)
Thank you for this! Took some debugging to figure out its including the quotation marks.Distinguished
Well, I just experienced it in development after changing a config file. Crazy part is that production runs on that exact config file since days - with single quotation marks where docker-compose reads the .env file and everything is fine. Development no throws unkown character ''' Now I am lost again in that developer's worst case scenario: It should not work, but it does.Deberadeberry
S
4

test 123 is a valid path name on UNIX. Try

PATH=test 123

It will return:

123: command not found

Or even

export PATH=test 123

which will return

bash export: `123': not a valid identifier

Does it answer your question?

Honestly I would not follow such fourth party style guides. Although I'm astonished that even Google advertises such wrong advices.

I would follow:

(to be carefully extended)

Smukler answered 24/10, 2015 at 13:31 Comment(6)
TLDP's guides are also third party, and they are also biased in many ways, e.g., avoiding bashism at times when bashism is clearly superior. Anyway, shell scripting is extremely flexible (very simple example: do you use curly braces around variable names?), and in the end it usually boils down to one's own judgement.Kelly
I agree, however the Google style guide was wrong in this case. I've should called it 4th party.. let me change thatSmukler
Well, it is wrong only if your literal part contains whitespace (which is bad practice in the first place) and you're confused enough to write down the assignment with the visible whitespace...Kelly
I should simply have explained that word splitting happens without quotes and what that means. I admit it is not my best answer ever.. If I'll have time later I'll give a proper explanation otherwise delete it.Smukler
You do realize expansion is different in assignment statements (with any preceding declare variant)?Kelly
See mkelement0's updated answer. It has an executable example. One thing I remembered wrong: in bash, it's okay even if assignment is preceded by a declare variant.Kelly
Y
0

Not path related, but same as in https://stackoverflow.com/a/48411223, I got bit as well in the context of docker compose.

I had a token env variable in my .env file, something like

MY_TOKEN="abc$123"

and got invalid login credentials errors because MY_TOKEN value was abc&123 in the container. The actual token was much longer so it was quite difficult to spot the problem.


MY_TOKEN='abc$123'

did the trick.

Yesima answered 11/10, 2022 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.