14

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.
R.D.
  • 297
  • 8
    ~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
  • @SergiyKolodyazhnyy I can confirm that foo is not a user in /etc/passwd. More details here: https://unix.stackexchange.com/questions/423962/what-is-a-tilde-when-used-as-a-prefix-to-a-path#comment763801_423983. FWIW, I'm being intentionally vague with paths as I see with my work computer and sharing the real path would likely reveal personal info. – R.D. Feb 13 '18 at 22:14
  • zsh "named directories" are explained in zshexpn(1) under "Dynamic named directories" and "Static named directories" - has anyone actually tried looking at the manual? –  Feb 13 '18 at 22:24
  • @R.D. What about /etc/master.passwd ? – Sergiy Kolodyazhnyy Feb 13 '18 at 22:29
  • @LelouchLamperouge Wrong duplicate link. Your link talks about history of tilde alone. This question talks about tilde expansion (which is more features than just expanding to user's home), and at least according to OP there is some unknown behavior for tilde expansion. – Sergiy Kolodyazhnyy Feb 13 '18 at 22:32
  • @SergiyKolodyazhnyy You're correct. Retracting my close vote. It was my oversight. – Lelouch Lamperouge Feb 13 '18 at 22:41
  • @SergiyKolodyazhnyy, no luck in /etc/master.passwd. I just grepped the entirety of /etc for instances of foo (or similar paths I'm able to access)--no dice – R.D. Feb 13 '18 at 22:57
  • @R.D. see RJHunter's and Abigail's comments under my answers. You might consider trying getent – Sergiy Kolodyazhnyy Feb 14 '18 at 06:10
  • @R.D. can you grep -Fr some/mount/point /etc ? [without any foo part, just the mount point itself; it may be listed somewhere as a default location for creating user directories] – Will Crawford Feb 14 '18 at 11:33
  • https://stackoverflow.com/questions/998626/meaning-of-tilde-in-linux-bash-not-home-directory – Julien Lopez Feb 14 '18 at 13:19
  • @JulienLopez I believe that's getting me closer! https://stackoverflow.com/a/27068161/639069 is spot on to my situation. Researching further, https://docstore.mik.ua/orelly/networking_2ndEd/nfs/ch09_04.htm perfectly describes what's going on my machine. Unfortunately, none of the mentioned configuration files contain what I'd expect it to. I'm going to mark this question as a dupe of the one you pointed to and hopefully contribute with a more thorough answer. – R.D. Feb 14 '18 at 20:16

2 Answers2

17

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
  • @ilkkachu Yep, you're right. I've tested that with adding testuser and modifying password expiration date. The username still appears in tab completion. I've removed that bit from the answer – Sergiy Kolodyazhnyy Feb 13 '18 at 22:07
  • 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 00: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 03: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
7

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/
WEBjuju
  • 506
  • 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