3

If I define a variable with the quotation mark:

TEMP="~/Dropbox"

then

ls $TEMP

would not work, instead

echo $TEMP | ls

works.

And to get the same result, I can also define the variable without the quotation mark, like:

TEMP=~/Dropbox

by doing so I can simply type

ls $TEMP

I am quite confused about the difference between them, and I am wondering when and where should I use these two kinds of definitions respectively?

2
  • @jasonwryan Thanks for this information. Could you give more explanation on this issue, for example, why are the behaviors of the two cases mentioned above different? Commented Jul 11, 2015 at 23:09
  • echo $TEMP | ls is not doing what you think. ls doesn't read from stdin, so the $TEMP piped to it is ignored. Try running the command when $TEMP is a different path than the current directory and you'll see.
    – jwodder
    Commented Jul 12, 2015 at 0:17

3 Answers 3

8
TEMP="~/Dropbox"

The above defines a variable that contains a literal tilde followed by a slash. Because it is in quotation marks, the shell does not expand ~/ to the home directory. Observe:

$ echo "quotes=~/" noquotes=~/
quotes=~/ noquotes=/home/john1024/

Thus, if you want ~/ to mean the home directory, then ~/ needs to be outside of quotes. The following works:

TEMP=~/"Dropbox"

Let's consider what happens if the ~/ is in quotes:

TEMP="~/Dropbox"
ls $TEMP

The above command will look for the file ~/Dropbox meaning a file named Dropbox in a directory named ~. Since you most likely do not have a directory named ~, ls will return with an error message: "No such file or directory."

Also, the command below does not do what you think:

echo $TEMP | ls

ls ignores standard input. A bare ls command just lists files in the current directory, whatever that directory is.

1
  • ~/... does not expand to a user's home directory then /..., but instead to the value of the shell variable $HOME followed by /.... Generally the two coincide - as HOME is generally exported to all child environments of /bin/login (and friends) - but $HOME is a shell variable and can be altered at any time like all the rest. Try it.
    – mikeserv
    Commented Jul 12, 2015 at 2:11
3

man bash and look for "Tilde Expansion"

or in short: an unquoted tilde(~) is replaced with the user's home, which doesn't happen anymore, if it is part of a variable.

$ T=~/Downloads
$ echo $T
/home/zstegi/Downloads

$ T="~/Downloads"
$ echo $T
~/Downloads
0
3

There are several aspects to this question as asked. In the first place, the effects of a quoted assignment might differ from the same assignment statement sans quotes for two reasons:

  1. If the assignment contains delimiting syntax tokens (such as ;<> or white-space) then quotes might serve to include these, whereas the unquoted assignment statement would delimit at the token.

    • In bash and some other shells the tokens () are special cases in that var=(...) serves to indicate an array assignment, though the same construct would be a syntax error without quotes in a POSIX shell which does not support the array extension.

      (   spc=  sc=; echo "<$spc>:<$sc>"
          spc=' ' sc=\;; echo "<$spc>:<$sc>"
      )
      

      <>:<>
      < >:<;>
      
  2. If the assignment contains expansion tokens quotes might serve to prohibit the expansion from taking place as might otherwise happen without them.

    • Any tokens contained within an expansion do not apply as the contents of the expansion are literally passed from the expansion to the assigned variable.
    • The " double-quote does not serve this purpose for $ denoted shell-expansions, and is therefore usually extraneous in an assignment statement, though it can be used to contain $ denoted expansions and other shell tokens in one quoted context.

      (   spc=\  sc=";" 
          tkns=$(printf "(<\n\'\t)>|")$spc$sc\"
          names=tkns$spc$"sc$spc$"spc
          printf ::$%s::\\n "$names" "$tkns"
      )
      

      ::$tkns $sc $spc::
      ::$(<
      \'  )>| ;"::
      

Shell tokens are very special characters to the shell - they are the characters which separate commands each from one another, and command words from command arguments. They are the characters which delimit/concatenate shell words. Shell tokens are the characters which a shell's syntax parser (or lexer) will be looking for and acting upon as it reads in a command before ever the shell has a chance to consider expanding any part of it.

But there are other characters which are almost as special to the shell. These are characters which affect shell expansions, such as those contained within the value of the shell's $IFS parameter, and filename expansion metacharacters like ?[*, and - as noted in your question - the ~ tilde.

Excepting the ~ tilde in special cases, these characters have no bearing on assignment statements - the assignment context (as opposed to the list context) is immune to both $IFS splitting and filename expansions. And so expansions which occur within a variable assignment need not be protected for literal values in the way you should in a command list.

And so the following works...

(meta=*?; meta=[$meta; echo "$meta")

[*?

The ~ tilde, though, is a special kind of shell expansion. Its expansion is handled very like a shell alias in that its contents are always expanded literally. ~ tilde and alias expansions both - like the more typical $ dollar-sign denoted expansions in assignment context - are always immune to $IFS and filename expansions. They differ, though, in that the ~ tilde is almost always expanded as an argument, and a shell alias cannot be expanded in that context unless it immediately follows another alias which has been expanded and whose value ends with a <space>.

A ~ tilde expansion will not occur within quotes of any kind, and, for that matter, will not occur at all unless it is properly delimited on all sides, or unless its trailing context names a system user and the shell can properly confirm this fact.

And so...

~some_user

...will expand either to the home directory of the user some_user if the shell can confirm such a user exists, or it will not expand at all.

To properly delimit a ~ tilde expansion you can use any of the shell tokens already mentioned excepting the quotes plus a / on the right-hand side in a list context, or, in an assignment context, either of / or : on the right-hand side or, on the left, either the first = in the assignment statement or : anywhere else.

When there is no trailing context, and the ~ tilde is properly delimited, then the ~ tilde will expand to the current value of the shell variable $HOME. This distinction is often mistakenly overlooked when discussing the ~ tilde expansion. A user's home directory and the value in $HOME need not be the same, as the shell variable $HOME can be reassigned or unset at any time like any other. In fact, when $HOME is unset, POSIX leaves the behavior of a lone ~ tilde's expansion unspecified, though bash, for example, will attempt a lookup with the system for the current user's home directory.

For example:

HOME="

:~"::~//~ bash -c '
    printf "\t<%s>" ~/ ~mikeserv/
    HOME=; echo
    printf "\t<%s>" ~/ ~mikeserv/
    unset HOME; echo
    printf "\t<%s>" ~/ ~mikeserv/
'

    <

:~::/home/mikeserv//~/> </home/mikeserv/>
    </> </home/mikeserv/>
    </home/mikeserv/>   </home/mikeserv/>

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