The tilde in your PATH may not be your HOME (disconnect3d.pl)

190 points by arusekk 9 hours ago

amarshall 8 hours ago

Or you can just…not quote the tilde. Folks always seem to reflexively quote “strings” in Bash while not realizing that (almost) everything is a string and most strings are not quoted and it would be odd to do it (e.g. no one is doing `"ls" "-a" "foo"`).

kragen 43 minutes ago

Not quoting the tilde doesn't help:

    : tmp; echo $PATH:~
    /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:~
Edit: mjmas points out that I am wrong, because different Calvinball rules apply to variable assignments:

    : tmp; x=$PATH:~
    : tmp; echo "$x"
    /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/user
Also in general your advice is very bad advice. In command-line arguments, which is the vast majority of the code of any shell script, you should always quote strings that contain variable expansion unless you want them to be implicitly split on spaces after variable expansion, because the thing you're storing in the variable is not an atomic string but rather a space-separated list.

This is almost never what you actually want, and in the rare cases that you do want to store a list, the shell's implicit space splitting usually breaks on filenames containing spaces. Bash has actual array variables which make it possible, but horrible, to handle this in a first-class way:

    : tmp; x=("foo bar" baz)
    : tmp; echo "${x[@]}"
    foo bar baz
    : tmp; touch "${x[@]}"
    : tmp; ls -l "${x[@]}"
    -rw-r--r-- 1 user user 0 Oct 11 17:52  baz
    -rw-r--r-- 1 user user 0 Oct 11 17:52 'foo bar'
This ksh feature is absent in the Bourne shell and in dash, but MirBSD ksh, Bash, and zsh all have it.

In general, whenever you see a $variable $expansion in a shell script outside of double quotes, you should suspect that the shell script will probably fail if your filenames or directory names contain spaces. In very many cases, this results in path injection security vulnerabilities. There are contexts where unquoted $variable $expansion is safe, but they are relatively rare.

However, relevant detail here! One of those safe contexts is actually variable assignment, where as mjmas pointed out in their helpful comment below, unquoted variable expansion is actually perfectly safe:

    : tmp; a='x  y'
    : tmp; b=α:$a:ω
    : tmp; echo "$b"
    α:x  y:ω

mjmas 23 minutes ago

Bash does it only inside variable assignments:

  a=123:~
  echo $a
  echo 123:~
results in:

  123:/home/me
  123:~

kragen 11 minutes ago

Cockbrand 7 hours ago

I feel like this is common knowledge and should not be worth mentioning. But then it apparently is not common knowledge, as the article proves.

Have I run into this at some point?

I certainly have.

Have I learned to quote better and only where appropriate from it?

I certainly have.

Bourne compatible shells take a while to learn and require some experience. This won't change, but alternatives exist, with their own caveats.

MathMonkeyMan 2 hours ago

Almost nobody that I've ever worked with knows how the shell works. I send them [the docs][1] but what kind of world would this be if people read the docs? Also, not exactly a page turner.

[1]: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V...

Cockbrand an hour ago

jolux an hour ago

isityettime 2 hours ago

This feature, or at least the syntactic unit in question has a name here "bare words". You also have these in some contexts in Perl and Ruby, YAML, and probably some other languages, idk.

I've also seen this even with people who seem like generally competent shell users. Idrgi

dylan604 8 hours ago

Unless you're running a shell command from python. That was the first time I saw a command string broken down into "string" arguments for every thing like that.

thwarted 7 hours ago

In that case, you're not running a "shell command" from python, you're passing arguments to exec. A shell command would be a string interpreted by the shell, and you'd use that for shell syntax things like having the shell do variable interpolation or redirections as part of executing the command.

cr125rider 8 hours ago

The trick is to quote explicitly and correctly. “ and ‘ are different.

dylan604 8 hours ago

why would you use smart quotes in a terminal like that?

tom_ 7 hours ago

airstrike 7 hours ago

paulddraper 7 hours ago

People quote both too often and too little.

GENERAL RULE

1. Double-quote dollar sign expressions, and nothing else.

  foo

  "$bar"/foo

  baz:"$(cat example.txt)"

  exec cmd "$@"
2. Single-quote words with a literal special character, and nothing else.

  'Die Hard'

  'ke$ha'
---

I should point out that the author's example is NOT fixed by different quoting though.

  # original
  export PATH="$PATH:~/.local/bin/"

  # without unnecessary quotes
  export PATH="$PATH":~/.local/bin/
Because tilde expansion only happens at the beginning of the word.

gray_-_wolf 3 hours ago

> I should point out that the author's example is NOT fixed by different quoting though.

Sure, but I would say you almost always want to add to the start of the PATH, not to the end.

GrinningFool an hour ago

JdeBP 6 hours ago

The Z shell's, C shell's, and others's syntaxes for setting the PATH environment variable via a shell array variable alias also does the tilde expansion.

    path=( $path ~/bin )
It's worth noting, also, that the path and manpath settings in login.conf(5) expand leading tildes in individual search path items.

* https://man.freebsd.org/cgi/man.cgi?query=login.conf&sektion...

So putting the addition of things like ~/bin to PATH in /etc/login_conf and ~/.login_conf instead of shell scripts is another way to address it.

    :path=~/bin /usr/local/bin /usr/pkg/bin /usr/bin /bin:
It's particularly handy when there are multiple login shells in use.

* http://jdebp.uk./FGA/BSDs-for-Linux-users/login-conf.html

vips7L 8 hours ago

Or just stop using bash. It’s a terrible language to write and has tons of footguns.

yjftsjthsd-h 6 hours ago

For the things shell is good at (running other commands, pipes, and shuffling files), I have yet to find anything even close to as good.

hnlmorg 5 hours ago

vips7L 5 hours ago

shevy-java 4 hours ago

CodesInChaos 7 hours ago

Unfortunately half of its badness isn't isn't the shell itself, but the convention of how parameters are passed to processes on Unix systems.

hnlmorg 5 hours ago

formerly_proven 7 hours ago

akoboldfrying 7 hours ago

sysguest 6 hours ago

malux85 6 hours ago

The widwit answer: "Dont do X" (and nothing more)

The enlightened answer "You should use A, B or C for these reasons"

Even though bash is installed on many systems, I try to encourage people to use better designed shells that have less of these footguns :

Fish (https://fishshell.com/): No implicit word splitting : spaces in variables won't unexpectedly become separate arguments.

Zsh (I use this: https://ohmyz.sh/): Arrays start at 1 by default, but crucially, unquoted variables don't implicitly split into multiple arguments.

Nushell (https://www.nushell.sh/): Passes structured tables and records between commands : avoids fragile parsing of text with awk/grep.

Grimeton 3 hours ago

You need to read the manual.

chasil 6 hours ago

I think the appropriate method by POSIX rules would be:

  export PATH="$PATH:"~/.local/bin
I may be wrong. If I'm not, that works in any POSIX-compliant shell.

Edit: this appears to work properly with mksh on my phone:

  :/ $ export PATH="$PATH:"~/.local/bin
  :/ $ print $PATH
  /product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin:~/.local/bin

yencabulator 5 hours ago

That's a literal ~ in your path, the exact problem the blog post talks about.

Your use doesn't count as a "word", per man bash. Tilde is only expanded to home at the start of a typically whitespace-separated word, and your tilde is in the middle of one.

> If a word begins with an unquoted tilde character (‘~’), all of the characters up to the first unquoted slash (…) are considered a tilde-prefix. (…)

> word A sequence of characters considered as a single unit by the shell. Also known as a token.

chasil 3 hours ago

SoftTalker 6 hours ago

I never use tilde in scripts, only as a convenience when typing commands interactively.

$HOME otherwise, which still has gotchas but they are the same as any other environment variable.

ndegruchy 5 hours ago

Yeah, in scripts I try to be as explicit as possible. `$HOME` for the home directory, `$XDG_DATA_HOME` for `/home/foo/.local/share/`, etc.

The more specific you are, the less gotchas you're going to fall to.

nlehuen 2 hours ago

I refuse to let knowledge about Calvinball-level grotesque rules about quoting and escaping in bash encumber my mind.

weinzierl 2 hours ago

Good decision and avoid the shell whenever you can. Too late for me. I did my fair share of shell programming (and learned if from some real masters) so that my mind is already encumbered. My confidence in being able to write a longer or more complex shell script with 0 errors is still 0, so it gives me nothing.

em-bee 8 minutes ago

in the days of yore, when i still was a green linux/unix newbie i attempted to use the mirror tool (written in perl i believe) to create a mirror of something on my account. i configured the tool to write the files into a directory in my home. in the config i wrote ~/somewhere ...

after running the tool, i discovered a literal directory named "~".

that's wrong i thought, fixed the config and proceded to remove the offending directory:

rm -r ~

then i waited ...

and waited ...

and i started wondering why removing an almost empty directory is so slow ...

...

...

oh sh!!!!!!

...

FeepingCreature 8 hours ago

Kind of seems like you should have noticed this by your ~/.local/bin PATH not working?

alexpotato 6 hours ago

Not necessarily about tildes but about some of the craziness that can happen with bash at large orgs:

At a past job, I was trying to figure out what part of my basrhc was setting a particular environment variable. I assumed that it must be some kind of default installed in my user profile and/or inheriting from /etc/<something>.

I realized pretty quickly that my bashrc was importing some other files. Again, the assumption was that this would be one file deep in the import.

It turned out there were 10+ layers of import starting from an "ur-bashrc" and then layer upon layer of more and more imports to finally get to a user level profile.

It was so convoluted that I was going nuts until I found this Stack Exchange post: https://unix.stackexchange.com/questions/813/how-to-determin...

It turns on "tracing" for bash imports so that you can then narrow down on where the env variable is getting set.

thefilmore 4 hours ago

You can just do:

  PATH=~/.local/bin:$PATH
PATH is already exported. Quotes are also not necessary for assignments.

stkdump 3 hours ago

Looks dangerous to put a user writable directory ahead of system directories in PATH

bityard 2 hours ago

Nah, it's totally normal because you often want to deliberately override or wrap system commands for various good and convenient reasons.

If your concern is that some hacker could put an illicit binary in ~/.local/bin, then your security problems are much deeper than your PATH order.

russellbeattie 3 hours ago

While we're all getting pedantic and esoteric, in zsh you can do this:

path+=(~/.local/bin)

You don't even have to use the "export" keyword. Not that I would do this, but since we're all pointing out random shell stuff...

(In zsh the lowercase "path" variable is an array that's automatically tied to the $PATH string, so you can just add to it using parens to signify new array elements, separated by a space.)

dspillett 7 hours ago

I was going to say “it always seems to work for me” then I saw “… actually works in Bash and Zsh, because …”.

Another Bash-ism I need to be careful not to use when trying to be portable.

It is worth noting that on a lot of systems /bin/sh isn't bash (or zsh) so if you want to rely on Bashisms (or just can't be bothered looking for them) be specific and use “#!/bin/bash” for you hashbang. On Debian and similar it is usually dash for instance.

opello 5 hours ago

For various embedded environments that use busybox it's busybox's brand of ash. I generally enjoy the opportunity to learn when bumping up against these kind of edge cases.

the__alchemist 7 hours ago

I wish Linux distros would ship a "Terminal"/"CLI" program etc that is decoupled from the scripting language. Have a universal Path env var that isn't tied to a specific shell. Lets you execute cd commands, launch python/git/cargo/arbitrary applications etc, and have a good bookmark + autocomplete system. It feels like the conflation of scripting language + CLI application is the root of these complications and subtleties.

If you are using shell scripting (And prefer Bash etc over Python), you would keep using Bash/Fish/Zsh etc. If you are using the CLI to launch applications that don't have a GUI, navigate directories and perform file system operations, then you would use the plain terminal.

delta_p_delta_x an hour ago

> Have a universal Path env var that isn't tied to a specific shell

This sounds like Windows. ;)

Environment variables are stored in the registry; for the user, it's

  HKCU\Environment
and for system-wide env vars, it's

  HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment

dicytea 7 hours ago

$PATH is not tied to any specific shell. And what you're describing is a shell, so I'm not sure how it's any different from existing solutions.

the__alchemist 6 hours ago

It is - this is why adding something to the Path is tricky on Linux. Or I should say, there is a mismatch between the common instruction of how to do it (export) vs what you have to do (e.g. edit bash config)

hnlmorg 5 hours ago

Lerc 7 hours ago

I always thought that there should be a proc style file system for simlinks to user specific data.

I'm not sure why this can't be done. Security people will have a million reasons, I guess it's possible one of them might be valid.

akoboldfrying 7 hours ago

IIUC, you're describing an extremely restricted (some would say underpowered) shell.

It sounds like you could make it yourself in ~10 lines of Python or bash. I don't see it catching on, though.

mrsssnake 26 minutes ago

Is there some tool, language or way to early script together inputs and outputs or external programs, with some safety or "proper" (for lack of better word) programming languages but with convenience of Bash (no wrapping like "exec(programname)"?

lexicality 8 minutes ago

Perl has been the sysadmin's friend (and enemy) for decades. You can run stuff with backticks and manipulate the results in relative safety.

ligarota 6 hours ago

Juste create a file ~ which is a symlink to home :)

Big brain time here

mnw21cam 2 hours ago

In every single directory you're ever going to have as cwd when running that software?

panzi 5 hours ago

I expected it to also talk about ~username. See e.g.: echo ~root ~pulse ~sddm

arkt8 5 hours ago

It is a clear example of RTFM! An expansion is no a variable. An expansion not expands under quotes. A variable expansion is not simply an expansion as it is between brackets.

Again RTFM instead of wait for a miracle of your agent.

3eb7988a1663 4 hours ago

Ehh, there are many, many (documented) gotchas about commonly used software that routinely bite people. Yes, it would be great if everyone were an expert in how every aspect of their toolkit works, but that is not practical.

very_good_man 8 hours ago

Reminds me of early days of Cursor when it decided it would be a good idea to create a directory named "~" in my repo root!

That was a scary mistake to unwind!

Ekaros 5 hours ago

Just maybe, just maybe reserving somethings is not a bad idea... Whatever the fanatics say... There is enough features in shell that maybe banning somethings would be correct design.

Grimeton 3 hours ago

People only reading the manual after they shot themselves in the foot.

Hilarious!

quotemstr 6 hours ago

I was expecting this article to be about skew between HOME environment variable and getpwent(3) (usually from /etc/passed) views of the home directory.

They can be different things. Suppose your user is foobar, ordinarily homed at /home/foobar. You can write HOME=/tmp/my-test-home. Then, ~/qux will become/tmp/my-test-home instead of the usual /home/foobar/qux. That's because bash and zsh use HOME to resolve ~.

Almost. If you write ~foobar/qux, you get /home/foobar/qux again because the ~-with-username syntax looks up the getpwent home directory not the environment one.

And of course language runtimes are all schizo about whether the "user home directory" API uses HOME or the getpwent database or whatever to determine the home directory.

It's a mess, TBH. It used to be useful to temporarily bind HOME to something else to do things like create isolated test environments. Now, because of the aforementioned schizo sprinkler of randomness in the environment, you're going to have a bad time if you don't keep HOME synced to getpwent home directory.

duncangh 7 hours ago

I should have read the docs before getting ~/ tattooed on my wrist.

lysium 6 hours ago

If you haven’t quoted it, I think you’re fine.

duncangh 3 hours ago

sometimes I feel like my environment isn’t properly configured but that’s just as likely a user error

ChrisArchitect 3 hours ago

the irony of the submitted url having a weird double slash // in it.

NotMichaelBay 3 hours ago

Alternative title: There's no place like $HOME

gjvc 8 hours ago

bad example in the article:

  export PATH="$PATH:$HOME/.local/bin/"
better:

  export PATH="$HOME/.local/bin/:$PATH"

Backslasher 8 hours ago

Afaik it's habit to give system paths precedence so a malicious script can't shadow e.g. sudo and steal your password, escalating a local file write into root

toast0 8 hours ago

Otoh, if you don't put your local path first, you can't override system binaries that you want to override.

Also, if something can write into your path, it can probably write to your shell config and/or the environment variables.

stkdump 3 hours ago

3eb7988a1663 2 hours ago

All sorts of utilities push themselves to the front of path: uv, mise, asdf, python virtual environments, nix shell, etc.

It is a theoretically nice ideal that fails immediately when you want project specific overrides.

jdxcode 31 minutes ago

paulddraper 8 hours ago

AFAIK it's habit to allow your scripts to override system ones, so you can customize behavior.

I've always seen home dir, homebrew, etc prepending to PATH.

marcosdumay 6 hours ago

That's important only for the people that add relative names (like '.') to their path.

Most people know better.

gjvc 6 hours ago

by that time it's too late and should have been prevented appearing on the host much earlier

IshKebab 9 hours ago

Bash footgun #238503.

paulddraper 7 hours ago

That's standard POSIX.

Tilde expands when at the beginning of an unquoted word.

Pretty straightforward.

  ~ or ~/ --> $HOME

  ~user --> user's home
---

Bash has a few extra.

  ~+ --> $PWD

  ~- --> $OLDPWD

  ~+N or ~-N --> dirs

Joel_Mckay 7 hours ago

In most use cases, the bash scripts location is more important than $HOME. This is because it is resilient to changes in user in the session, and parent process current working location contexts. =3

scriptPath=$(/usr/bin/realpath "${BASH_SOURCE[0]}")

localPath=$(/usr/bin/dirname "$scriptPath" )

/usr/bin/echo "localPath = '$localPath'"

_ZeD_ 8 hours ago

well.. it's in zsh (and probably any other *sh) too

vbernat 8 hours ago

In Zsh, you can set PATH with path=(~/.local/bin $path). There, it works as shell expansion works.

kccqzy 8 hours ago

mr_mitm 8 hours ago

So many headaches could be avoided if we only allowed `[A-Za-z0-9._-]` in paths. (Arguably, even `-` can be problematic.) Encoding issues, expansion, parameter separation, ... and I never saw a convincing case in favor of supporting anything else.

jetbalsa 8 hours ago

What about people who do not speak English? or even use Latin letters?

mr_mitm 8 hours ago

The language I grew up with does use non latin letters, I can manage. Then again, it's only four of them, so I get your point ... but I can dream, can't I?

ThunderSizzle 8 hours ago

What Latin letter(s) are you referring to?

The (classical) Latin alphabet can be fully described by the English alphabet.

toast0 8 hours ago

cowboylowrez 8 hours ago

Dylan16807 7 hours ago

If you're trying to avoid headaches then definitely don't allow -. At least not as the first character in a segment.

And allowing any letters from any language is probably worth the hassle.

dylan604 8 hours ago

I always joked about setting a custom keyboard layout to replace the space char with the underscore char specifically for avoiding spaces in file paths.