11

Edit: This is a duplicate of https://stackoverflow.com/questions/998626/meaning-of-tilde-in-linux-bash-not-home-directory/. I don't have the reputation to close this question as duplicate.

I'm not referring to ~ as in the home directory but rather this:

$ ls ~foo/bar
/some/mount/point/foo/bar

However if I attempt it with a different mount point, e.g.:

$ mount | ag "/dev "
devfs on /dev (devfs, local, nobrowse)
$ ls /dev/stdin
/dev/stdin
$ ls ~stdin
zsh: no such user or named directory: stdin . 
# bash has a similar error message: 
ls: ~stdin: No such file or directory

What is the ~ called in this context? How does it work?

Edit: More information based on some of the comments below:

  1. I can attest that foo is not a username on my system.
  2. When attempting to autocomplete ls -lah ~ not all options are shown. i.e. I'm able to cd ~qux, when qux doesn't show up in the autocomplete. Again qux is not a user in my system.
  3. If it matters /some/mount/point is a network share.
  4. All of the details suggest some named path muckery, a Z shell feature of pathname expansion, but this works in bash as well, which apparently doesn't support things like the Z shell's named paths.
  • 5
    ~foo is the home directory of the user foo. If the user is not specified, the current user is the default. – DopeGhoti Feb 13 '18 at 20:30
  • But in this case, /some/mount/point definitely isn't my home directory. cd ~ takes me to /Users/$username/--which matches $HOME – R.D. Feb 13 '18 at 20:32
  • 1
    zsh appears to also use the tilde to indicate named directories. – DopeGhoti Feb 13 '18 at 20:36
  • I suspected that too!! Except for some reason the series of commands I posted above works in bash as well (bash -c "ls ~foo/bar")--which doesn't have named directories. Furthermore even within zsh, if I inspect the env, I don't see any named directories set up. I'm on Mac OS and I feel this is some feature specific to OS X. – R.D. Feb 13 '18 at 20:42
  • 1
    You only said ~foo. Take the actual string (not the example foo ) and do grep "actual username" /etc/passwd. ~text should work for only possible login usernames according to bash manual (doesn't necessarily mean it is actually able to log in; in case of system users such as ~lp , for example). In all my tests, the ~string corresponds with string being username. – Sergiy Kolodyazhnyy Feb 13 '18 at 21:15
14

What is ~foo

Quote from bash manual (with added emphasis):

If a word begins with an unquoted tilde character (`~'), all of the characters preceding the first unquoted slash (or all characters, if there is no unquoted slash) are considered a tilde-prefix.If none of the characters in the tilde-prefix are quoted, the characters in the tilde-prefix following the tilde are treated as a possible login name.

~foo expands to foo user's home directory exactly as specified in /etc/passwd. Note, that this can include system usernames; it doesn't necessarily mean human users or that they can actually log in locally ( they can log in via SSH keys for instance).

In fact, as noted in the comments, bash will use getpwnam function. That function itself is specified by POSIX standard, hence should exist on most Unix-like systems, including macOS X. This function isn't limited to /etc/passwd only and searches other databases, such as LDAP and NIS. Particular excerpt from bash source code, tilde.c file, starting at line 394:

  /* No preexpansion hook, or the preexpansion hook failed.  Look in the
     password database. */
  dirname = (char *)NULL;
#if defined (HAVE_GETPWNAM)
  user_entry = getpwnam (username);
#else
  user_entry = 0;

Practical example

Below you can see tests with system usernames on my system. Pay attention to corresponding passwd entry and result of ls ~username

$ grep '_apt' /etc/passwd
_apt:x:104:65534::/nonexistent:/bin/false
$ ls ~_apt
ls: cannot access '/nonexistent': No such file or directory
$ grep '^lp' /etc/passwd
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
$ ls ~lp
ls: cannot access '/var/spool/lpd': No such file or directory

Even if for instance _apt account is locked as suggested by output of passwd -S apt it is still showing up as possible login name:

_apt L 11/29/2017 0 99999 7 -1

Please note: This is not macOS specific feature, but rather shell-specific feature.

  • I doubt the shell can rely on finding out anything about the account expiration date or if it's locked or anything when expanding the tilde. E.g. on Linux, the expiration date is stored in /etc/shadow, along with the other protected authentication information, like the password. The reference in passwd is just about making the password invalid: that would not prevent authentication through other means. – ilkkachu Feb 13 '18 at 21:57
  • 3
    To be more exact, bash (not any other shell) will not look in /etc/passwd. It will get the information from getpwnam (or from a related function). Which may get its information from /etc/passwd, or it may get its information from Active Directory, LDAP, NIS, or something else. – Abigail Feb 13 '18 at 23:25
  • 1
    The only problem with this answer is that it quotes the bash man page (and old one at that, it seems). The OP is actually using zsh (could have been clearer), though in the comments it is mentioned that bash has the same behaviour. – Ken Wayne VanderLinde Feb 14 '18 at 0:31
  • 2
    You can use getent passwd foo instead of grep foo /etc/password to follow the same lookup mechanisms as the shell - perhaps including network-based directory services that @Abigail mentions. – RJHunter Feb 14 '18 at 3:39
  • 1
    Accepted as answer for the 'such as LDAP and NIS' bit, which ended up being correct in my case! – R.D. Feb 14 '18 at 20:21
5

In summary of why you are seeing something for ~foo/bar, it is because you have a user named foo on the system with a folder named bar in their home directory.

See this solution in another community that explains why (tilde) ~ is more than just "home directory".

If you have a user named bin on your system, then you can list the contents of bin's home directory by the command:

ls ~bin

One other thing you can try is to use tab completion after typing the following at a prompt (don't carriage return, just use the tab key):

ls -lah ~ tab

to see the list of users' home directories that ~ will expand to if you continue to. Example (truncated) output of tab completion of ls -lah ~ tab

$ ls -lah ~ [tab]
~antman/            ~games/
~bin/               ~mail/
  • 1
    +1 for tab completion. This clearly reveals all the usernames that bash can get as possible login names from /etc/passwd. It immediately helps to clarify what is going on if the user of course is aware of the usernames they have on the system. – Sergiy Kolodyazhnyy Feb 13 '18 at 21:35
  • 2
    Thanks for the tip on the bash completion--resulted in some interesting autocomplete results. While ~foo did autocomplete, I can attest that foo is not a user on my system (and indeed doesn't exist in /etc/passwd). Furthermore, all the folders/files in /some/mount/point show up in the autocomplete which is super interesting. – R.D. Feb 13 '18 at 22:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.