The tilde in your PATH may not be your HOME
disconnect3d.pl
disconnect3d.pl
Using ' and " to pretend to be ‘/’ or “/” is on par with the typewriter days where people would use the l key to stand in for 1 also. A justifiable approximation when technology limitations prevented using the real deal, but an approximation all the same.
The equivalent of l for 1 is doing font-specific pseudo smart quotes with ` and '
" can be inches or arcseconds from cartography.
' can be feet (of measure) or arcminutes from cartography.
These actually all have slightly different symbols, and the symbols for the left/right quotation marks are actually farthest from this approximation, even if they are the most frequent usage.
It’s always been wrong in some respect to use a single available symbol to emulate three or more different typographical tasks, but quotation marks may actually be the one where the emulation is the most wrong.
″ Double prime = Inches, seconds, or second derivative
‴ Triple prime = Third derivative or specialized notation
And a diversion: https://en.wikipedia.org/wiki/Ditto_mark
’Tis brillig, and the slithy toves
is not a left quote ‘ but an apostrophe ’, like the apostrophe in “can’t”; but ‘tis certainly possible that someone might want to put single quotes around a phrase beginning with the word “tis”, such as the Latin ‘tis misereri’. By contrast to “can’t”, the ‘ in “Hawai’i” is the ’okina used to represent the glottal-stop sound in the Hawai’ian language, not an apostrophe.I don't use WYSIWYG editors anymore, so I have to remind myself to use those when writing raw HTML text. Although, most browsers correct raw ' and " symbols in text now, I still try to use them to have compliant HTML
Want to quote a command line that may already contain double quotes, in order to pass it as an argument to some other program? No, you don't. It isn't right to want that.
In practice almost every application does still need to do its own parameter parsing; a flat array is not enough.
sometimes, its footguns seem worse than javascript...
hope some typescript-like "typed shell" becomes mainstream someday
The trouble with trying to invent a new, more robust shell language is you basically just end up re-inventing any number of scripting languages (eg Perl, Python, awk, ...), so you might as well use one of those.
I’ve done a fair amount of research here and I actually think we do need a new programming language for the shell (and then I created one).
I wrote a blog about this problem: https://murex.rocks/blog/split_personalities.html#conclusion
I use Take Command (well, mostly TCC) in my Windows workstations but no one else does, so TCC specific batch files and aliases only work for me.
If your colleagues want to reuse the same scripts then they can install the runtime too. It doesn’t mean they need to write all their future scripts using that runtime.
Did someone say... Xonsh?! It's tough to give up the niceties of zsh and it's various plug-in ecosystems, but I would love to be able to use Xonsh [1] as a daily driver for exactly the reasons stated. It is already the language I lean on over bash for most utility scripts with its kitchen sink built-ins.
[1] https://xon.sh/
Might want to check out nushell (https://www.nushell.sh/)
Windows, on the other hand, only passes one string. So it’s up to the application to choose how to handle whitespace, quotation marks, and other nuances with parsing parameters.
Variable expansion in Bash is lazy. But there’s no reason why variables cannot be tokenised so that strings with spaces aren’t treated as multiple parameters. And in fact that’s exactly how some other shells work, such as the one I maintain.
A typed array (e.g. by having a required single byte marker at the start of each string), or even nested structures similar to s-expressions would have avoided this.
I even considered writing a wrapper around some commands to send -- regardless of whether the user includes it or not. But that’s error prone too because
1. it’s a GNUism so you can’t guarantee compatibility with any tools that don’t call GNUs flag parsing library (which is particularly problematic outside of Linux)
2. If there a file called “-f” (for example), how do you know if the user intended that to be called as a file reference or a command flag? You then need to bake in a bunch of additional syntax sugar to make that explicit. Which results in a pretty awful user experience
3. The maintenance overhead for this would be astronomical
If UNIX were designed from day one to have that parameter type passed then this would be a very simple problem to solve. But the PDP machine UNIX was originally designed to run on wouldn’t have been powerful enough for that anyway. So once again we are limited by an architecture designed to run on mid-range systems of the 1970s.
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.
Such as Fish, nushell, Elvish, or the project I help maintain, “murex”
Unfortunately though, it’s not generally that hard to switch between different shells. I hear the argument you made a lot from other people too and yet we all acknowledge you should be able to code in Bash and a “proper” programming language (Python, Go, JavaScript, C, Rust, whatever). In fact I’ve learned probably 2 dozen languages in my career and it’s rare when I need to pause and remind myself how to do x in y.
Or to put it another way: you don’t say “I’m not going to use $IDE on my dev machine because I’m stuck with vi on the remote server”.
But if people don’t feel comfortable working in two different shells and that is the dealbreaker for using murex, or any other alternative shell, then I’m not going to hold that against them. Everyone has their own preference when it comes to workflows and productivity tools.
Powershell is, as I understand it, not available on Linux but is omnipresent on Windows, so hopefully the windows folks can use it like they would bash.
Because Windows is tied together through APIs and databases and not text files (unlike Linux), everything is text is not as useful as something that can talk objects. And a lot of Windows is object oriented, from the window message dispatching system through COM and down to NT's object manager.
Do note that Windows Powershell and Powershell are NOT the same. The following does work in Powershell, but not Windows Powershell.
echo hello && echo hello
Specifically Windows Powershell does not support `&&` or `||` (the main difference I immediately notice) and probably a lot of other things are different.Ruby replaced all my shell needs. Almost 25 years ago. I even have a shell written in ruby (it handles both bash-like behaviour as well as ruby code as-is); admittedly it is not quite perfect for everything, but I improve on it steadily. And it works on Windows too, which was one reason I wrote it in the first place (need to have it work via cmd.exe as-is).
Never looked back to shell. It is too awful to use.
shell - simple scripts of a low size/complexity
readable.
exceed that and I switch to python. Right off the bat, argparse and os.path make so many things reliable/secure/better.
Thing is, some of my coworkers don't know linux, so AI writes their scripts.
When I'm consulted on a review, it is pages and pages of shell scripts.
It is not easily readable stuff. There are lots of obscure/advanced bash features, like "if [[", and arrays and set (wierd option) and more...
I have to slog through it like I'm reviewing 100m of dense line noise. I despair. Even reading the CRC handbook or war and peace would be more pleasurable.
And in the end it is like reading a hostile privacy policy that I am obliged to accept.
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. 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.Sure, but I would say you almost always want to add to the start of the PATH, not to the end.
> Because tilde expansion only happens at the beginning of the word.
Nope, works fine:
$ PATH=/bin/
$ export PATH="$PATH":~/.local/bin
$ echo $PATH
/bin/:/home/izkata/.local/bin
I guess it's being smart about the ":" since this doesn't work: $ PATH=/bin/
$ export PATH="$PATH":foo/~/.local/bin
$ echo $PATH
/bin/:foo/~/.local/bin
And even just putting the ":" inside the quotes and "~" outside doesn't work: $ PATH=/bin/
$ export PATH="$PATH:"~/.local/bin
$ echo $PATH
/bin/:~/.local/binHave 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.
[1]: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V...
[Insert mild rant on kids these days having no attention span any more]
We used to try a command and then alter it slightly to see what it would do.
Really? I can’t be the only one that will spend hours reading this kind of stuff. You guys don’t read man pages for fun?
Years ago I thought this was fun. Now I've given up on learning zsh _arguments and the like. Instead I have made my software more interactive (an LLM helps here) and wrote it in Common Lisp (even without an LLM) so that I get decent completion behavior in SLIME for free.
I actually do like reading these things. But big shells like Bash and Zsh are brutal, especially with their random grab-bags of features that come from multiple shells of years long gone.
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/binYour 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.
Perhaps a succinct and compliant expression could be:
export PATH="$PATH:$(printf %s ~/.local/bin)"
That comes at the cost of forking a subsell.Edit: 2.6.1 Tilde Expansion in the POSIX standard says that ~ may be expanded "following any unquoted <colon>".
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...
That being so, the most succinct and compliant version is my first variant, with the colon moved outside the quotes:
export PATH="$PATH":~/.local/bin
Thank you for prompting me to look this up.I've also seen this even with people who seem like generally competent shell users. Idrgi
: 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:ω a=123:~
echo $a
echo 123:~
results in: 123:/home/me
123:~It does not warn about `echo "$PATH":~`, though, but that is perhaps an uncommon “error” that is more about your contrived test case (not that Calvinball is good, ofc).
Personally, I don't like using the tilde and would use "${HOME}" instead (I nearly always put variables in curly brackets as a matter of habit).
$HOME otherwise, which still has gotchas but they are the same as any other environment variable.
The more specific you are, the less gotchas you're going to fall to.
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.
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.
PATH=~/.local/bin:$PATH
PATH is already exported. Quotes are also not necessary for assignments.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.
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.)
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.
That was a scary mistake to unwind!
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.
It sounds like you could make it yourself in ~10 lines of Python or bash. I don't see it catching on, though.
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.
Env vars are not global. They just have that illusion because a fork() by default will pass your running env vars to the child. Thus trickling that value down.
Not all applications executable match the application name (eg Visual Studio Code is just “code”). So could that have been the issue?
> So could that have been the issue?
No. Adding things to the path is a reasonably common operation on all OSes and CLI-based software. Sometimes the installer will handle it for you; sometimes it won't. (This may be out of date, but I believe the Rust install script is an example of one that sets it up; Go is one that doesn't, and its official instructions CAO 6 months ago were to use Export, which doesn't work once you reboot)
More broadly speaking, you shouldn’t really be relying on your compiler to do the install. That was never the intended behaviour of a compiler. Which is why history you’d find the install options in the build scripts (eg makefile).
Distro maintainers test dependencies across all these different pieces of software, but they cannot test against people randomly installing their own versions of software. So it would be negligent for the Go team to advocate overwriting the distro-tested version of Go
Installing arbitrary software, including through the creator's official instructions, is a core capability of a GPOS.
> people randomly installing their own versions of software.
There are 2 things to unpack: Why you are referring to an official release as "randomly installing my own version", and that installing arbitrary software isn't a core component of a GPOS. Further, what does a call-to-only-use-package-managers (Or did I misunderstand you? That's how I'm reading it) has to do with the PATH on linux being terminal-specific?
No, it’s actually not. And when you look at the most popular platforms out there, they do have a primary channel for installing software.
> Why you are referring to an official release as "randomly installing my own version",
It’s not an official release from your OSs package repository. Which is what I actually said.
> and that installing arbitrary software isn't a core component of a GPOS.
I didn’t say that either. I said you’re installing it outside of the package manager so it shouldn’t mess with the platform defaults because it could unknowingly screw up another part of the system that the maintainers are depending on.
If you were to install your own C++ runtime onto Windows and overwrite the Windows default, causing Windows to develop a plethora of bugs. Would you then complain to Microsoft?
This is the equivalent of what you’re doing on Linux and which is why the Go official documentation talks about doing it in a more local scope. Just Google any of the stack exchange and forum discussions around the dangers of updating the system version of Python and Perl. People have done real damage to their systems by overriding the distros shipped version.
No, it’s actually not. And when you look at the most popular platforms out there, they do have a primary channel for installing software.
```
We have very different ideas of what we want out of an OS! I rarely install applications from package managers because the software I use is usually not on them, or is an out of date version. I don't post my own software on them as I'm not going to register and post updates for each OS. I do not think software should be fragmented across incompatible OS app stores.
You’re misrepresenting what I said again.
You argued that installing something outside of a package manager is a core requirement for an OS. And I said it isn’t because what you describe isn’t part of the technical definition of an operating system.
Now you’re arguing that I don’t want to be able to load software outside of package managers, and clearly that isn’t something I said. I’m a massively heavy Linux user and a FOSS maintainer. So I very much do install stuff outside of package managers all the time. But the difference between you and I seems to be that I understand any deviations to the host packages need to be installed elsewhere to avoid conflicts with the OS maintainers versions.
> I don't post my own software on them as I'm not going to register and post updates for each OS. I do not think software should be fragmented across incompatible OS app stores.
I do publish my software to multiple different package managers on different platforms. So I understand the problem well ;)
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\EnvironmentBig brain time here
Meaning everything in the path should resolve to sane stuff, like no relative paths, don't allow PATH=.:$PATH and no literal '~' characters (or even space/etc?) maybe check all are directories and exist.
I guess it is interesting that ~ behaves differently from a variable, as usually you'll have to use single quotes to get the string literal, but then I wouldn't want the tilde to expand inside quotes, either.
export PATH="$PATH:$HOME/.local/bin/"
better: export PATH="$HOME/.local/bin/:$PATH"I've always seen home dir, homebrew, etc prepending to PATH.
Also, if something can write into your path, it can probably write to your shell config and/or the environment variables.
Most people know better.
It is a theoretically nice ideal that fails immediately when you want project specific overrides.
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!!!!!!
...
prefix ambiguous paths with ./
rm -r ./~ would have worked.
don't use rm -r when you can avoid it.
rmdir -p ~/something would have failed without causing damage.
rmdir -p ./~/something would have been the best option.
if there are files but no subdirs inside then
cd dir
rm ./* (if there is a more specific wildcard that catches all files then use that)
cd ..
rmdir dir
is my preference now.Again RTFM instead of wait for a miracle of your agent.
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 --> dirsscriptPath=$(/usr/bin/realpath "${BASH_SOURCE[0]}")
localPath=$(/usr/bin/dirname "$scriptPath" )
/usr/bin/echo "localPath = '$localPath'"
Hilarious!
csh was also the most usable interactive shell in the early BSD days, since it had full support of job control, which was indispensable for power users before the advent of screen, and without the flexibility of windowing such as X11.
csh was really atrocious for scripting, though. Any competent programmer would eschew csh scripting in favor of Bourne or Korn. (In fact, we also avoided Bash like the plague, due to all the nonstandard extensions and initial non-portability.) I had friends whose employers sort of mandated csh scripts, and I pitied/mocked them for a long time. It was always a bad decision.
However, I'm gonna say that "~" was also a triumph of csh syntax, because, well, it was adopted into everything else! It's an indispensable shortcut now, and like job control, it has survived the acid test that ultimately killed csh and most of its stupidities. Just look at how it was seamlessly incorporated into web URLs.
An important distinction for "~" is how it can expand others' $HOME with an /etc/passwd lookup! There is no environment variable that's capable of that. It's such a command-line timesaver for people who collab.
~ is ubiquitous now, and that's not a mistake. Indeed, script programmers should adhere to ${HOME}, but when it comes to command-line quickies, there's noplace like tilde!
The (classical) Latin alphabet can be fully described by the English alphabet.
And allowing any letters from any language is probably worth the hassle.