13

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.
  • 287
  • 2
  • 8
  • 7
    `~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
  • Possible duplicate of [Why was '~' chosen to represent the home directory?](https://unix.stackexchange.com/questions/34196/why-was-chosen-to-represent-the-home-directory) – Lelouch Lamperouge 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.

Sergiy Kolodyazhnyy
  • 16,187
  • 11
  • 53
  • 104
  • 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
  • 496
  • 2
  • 4
  • 13
  • 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