If I introduce an alias (like `grep='grep --binary-files=without-match --ignore-case --color=auto`) that matches the name of a system binary - I probably do that intentionally.
And if I EVER need to call grep without my alias - I just prefix it with a backslash: \grep will search with case sensitivity and no color and will scan binaries.
Yes, I can do path ordering to override usual commands. However, having a set of odd-job scripts which start with a comma gives a nice namespacing capability alongside a well narrowed-down tab-completion experience.
While it's not the neatest thing around, it works surprisingly well.
Another idea which looks useless until you start using is text expanders (i.e.: Espanso and TextExpander).
Feel free to add the required ones while reading this comment.
Sorry for the inconvenience this might create.
I've never had this collision problem yet, despite appending my script directory to the end, but I'll use either of the above solutions if that ever becomes a problem.
alias curl='/opt/homebrew/opt/curl/bin/curl '
alias rsync-copy='/opt/homebrew/bin/rsync -avz --progress -h '
alias rsync-move='/opt/homebrew/bin/rsync -avz --progress -h --remove-source-files '
alias rsync-synchronize='/opt/homebrew/bin/rsync -avzu --delete --progress -h '
alias rsync-update='/opt/homebrew/bin/rsync -avzu --progress -h '
alias vi='/opt/homebrew/bin/vim -S ~/.vimrc'
alias vim='/opt/homebrew/bin/vim -S ~/.vimrc'
alias wget='/opt/homebrew/bin/wget -c '
There are others with flags added. These are the ones that override the builtin MacOS versions that aren't up-to-date.You use shadowing to fix issues where you install some software that expects you to have a sane and ~recent version of some tool like git, but you don't as your system provides that binary and unfortunately it is either not sane (not GENERALLY sane [while it could be sane for system scripts]) or not recent enough. In that case the program's function would simply fail if it would call the system's binary and you shadow the binary with your version to fix that.
> adding your script directory in front of the PATH
That's a poor advice for the scripts you call relatively frequently. Instead, (as a general approach, we aren't discussing some particular script) don't use shadowing for scripts: just pick a non-conflicting script name and append the script's dir to $PATH.
Why? It protects you from someone else (cough updated packages introducing new commands cough) picking a name you already use.
Thanks, mathfailure - this genuinely improves my life!
Call it the Chesterton’s Fence of ‘which’.
And then there’s Claude. It deletes whatever it finds at ~/.local/bin/claude, so I have to use a shell function instead to invoke the full path to my wrapper.
alias claude="sandbox-exec -f ~/agents-jail.sb ~/.local/bin/claude --dangerously-skip-permissions"My goal is to prevent Claude from blowing up my computer by erasing things it shouldn't touch. So the philosophy of my sanboxing is "You get write access to $allowlist, and read access to everything except for $blocklist".
I'm not concerned about data exfiltration, as implementing it well in a dev tool is too difficult, so my rules are limited to blocking highly sensitive folders by name.
Because I cannot imagine much 3rd party scripts working with random flags added to core tools
Random flags added to core tools are done with aliases, which do not affect the launched processes, not by shadowing them in ~/bin. Shadowing in ~/bin are for cases where a newer (compared to the system-wide version) or custom version of a tool is needed.
On MacOS I shadow that way just curl and git binaries to the versions installed from homebrew and nothing has broken (yet). I know that tar on MacOS is also a weirdo that I'd rather shadow with the homebrew's gtar, but their args are different and I of course understand that there's a high probability of something in system to be bound to mac's version of tar, so here I better remember to use 'sane' tar as gtar or use an alias (instead of shadowing the binary) for tar to use gtar (because aliases are for users, not for system scripts/processes).
And on my home desktop's Debian - I don't even use shadowing of binaries at all (never needed it).
Also, I just realized: I change PATH env via my shell's rc script (~/.zshrc), so I probably could worry even less about shadowing system binaries (like tar on MacOS) possibly breaking things.
Btw on the second suggestion, I think there's a command named `command` that can help with that sort of thing, avoids recursive pitfalls.
Don't tell people to sacrifice agency for apocalypse insurance that doesn't work, lol
which <commandname>... and breaks existing scripts that reference the system one, right?
If I ever wanted to achieve what you initially wanted to achieve - I could use something like
alias -s sh=sh
alias -s bash=bash
alias -s zsh=zsh
Just like I do bind .txt and .conf to 'less', .pdf to 'qpdf', .json to 'ijq', video formats to 'mpv' and so on.
This can also happen by downloading something off the internet (git clone, or tar xz foo.tar.gz), or on a multi-user system (eg. someone can put any of these common commands into /tmp/ and wait for you to drop into it and try a "ls" there) — if you have any untrusted content anywhere, you are exposed.
Not if if you APPEND the dot path to the PATH env: the system traverses the dirs specified in the PATH env from left to right and stops at first match. Your system's ls binary is in the dir that's to the left of your '.' dir.
Oh, that's without even going into the security risks and loss of portability.
Might even be as simple as “detect if I’m running gnu sed or bsd sed and use the appropriate one”. Obviously you can not have this problem by being smart about other things but defense in depth right?
Easy autocomplete, I know there won't be any collision, and which command is mine.
The problem in other distros is that if you prefix PATH so that it contains your executable "foo", and then run a program that invokes "foo" from PATH and expects it to do something else, the program breaks.
With Nix, this problem does not exist because all installed programs invoke all other programs not via PATH but via full absolute paths starting with /nix/store/HASH...
NixOS simultaneously smooths the path to using absolute paths while putting some (admittedly minor) speed-bumps in the way when avoiding them. If you package something up that uses relative paths it will probably break for someone else relatively quickly.
What that means is that you end up with a system in which absolute paths are used almost everywhere.
This is why the killer feature of NixOS isn't that you can configure things from a central place; RedHat had a tool to do that at least 25 years ago; it's that since most of /etc/ is read-only, you must configure everything from a central place, which has two important effects:
1. The tool for configuring things in a central place can be much simplified since it doesn't have to worry about people changing things out from under it
2. Any time someone runs into something that is painful with the tool for configuring things in a central place, they have to improve the tool (or abandon NixOS).
/nix/store/grep-hash -flags files | /nix/store/head-hash
instead of: "grep -flags files | head"?
For non one-off sorts of things, you would substitute in the nix expression "${gnugrep}/bin/grep" the "${gnugrep}" will expand to "/nix/store/grep-hash" and also make a dependency on the gnugrep package, so that the grep install won't get garbage-collected as long as your package is still around.
Here's an example[1] from a package expression for e-mail client I use, which will shell out to base64 and file. Upstream relies on these two programs being in $PATH, but this replaces the string used for shelling out with the absolute path in the nix store.
For shell scripts, I'll just do something like this near the top:
GREP="${GNU_GREP:-$(command -v grep)}"
Then I use "$GREP" in the script itself, and develop with grep in my path, but it's trivial to prepend all of my dependencies when I bundle it up for nix.1: https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/no...
[user@nixos:~]$ which grep
/run/current-system/sw/bin/grep
[user@nixos:~]$ ls -l /run/current-system/sw/bin/grep
lrwxrwxrwx 1 root root 65 Jan 1 1970 /run/current-system/sw/bin/grep -> /nix/store/737jwbhw8ji13x9s88z3wpp8pxaqla92-gnugrep-3.12/bin/grep
Basically, it is still in your environment, so I don't see how he can claim that this problem doesn't exist in Nix, unless you use flakes like a proper Nix afficionado.It does contain the issue a bit though:
I'm running isync in a systemd service, yet the program "mbsync" is not in my path. I have several services installed, yet their programs aren't in my path. My e-mail client shells out to "file" for mime-type verification, yet "file" is not in my path.
Run "compgen -c |wc -l" to get a list of commands; its over 7000 on my Ubuntu system and right around 2000 on my NixOS system.
As an aside, the packages that put the most executables in my path are probably going to be in the path for most NixOS installs (231 just for coreutils+util-linux):
27 /nix/store/csxa6mi2mpjl9vqxbv2j0bha6sz6nbjw-cups-2.4.14
31 /nix/store/334afxah19b3hr6ll93pfxlcyhhh2vws-pipewire-1.4.9
31 /nix/store/h2jsb5i4yfblr2f3ac2c7zpmlmj7zjym-perl-5.40.0
33 /nix/store/914x32c982bs3i1998yxvkg9svm3ycr5-shadow-4.18.0
33 /nix/store/a6s3hzj3b2z6rsyfkjyxwn265iyfl2gn-mtools-4.0.49
33 /nix/store/jky7jszaci5n7g426wf6nsg5dmik9nfw-kbd-2.9.0
37 /nix/store/2v1l6mqz0d7mfpp4ksw2048v3g0a1a19-hplip-3.25.2
45 /nix/store/90wlc37ljr6rpy2lan46bp0gq19vbgl5-iptables-1.8.11
48 /nix/store/1byhxs7b28grh8s15jc2dvs2zg36swjb-lvm2-2.03.35-bin
61 /nix/store/9xwxjkrwxjsvc5gs1l0syr4wbfvvvvcn-bluez-5.84
64 /nix/store/zf8qy81dsw1vqwgh9p9n2h40s1k0g2l1-systemd-258.2
72 /nix/store/1igrj9w84w7s3r80l3nkxcqwd84sw9mz-qemu-10.1.2
106 /nix/store/v4q3154vdc83fxsal9syg9yppshdljyk-coreutils-full-9.8
125 /nix/store/3c6r8gh8zrqw8xmncmlj9vivz9rz6r30-util-linux-2.41.2-binWhat can be a problem is muscle memory, when you expect it to autocomplete one way and it doesn't because something you want now shares first two or three letters with something else in your path. That's where FIGNORE comes in.
It does seem to do some more complex stuff now that would've been annoying, but not impossible, to write as a shell script.
You put keyseqs in ~/.inputc, set a keyseq-timeout, and it just works.
openssl ,v
to expand to... openssl --version
readline seems like the way to go.Then again most of the examples OP gave are usually available as short options, and aliasing ,s to sudo is certainly possible. So the only one which makes sense to me is ,,=$. But it's probably not worth the trouble to my muscle memory.
Every tool and shell that lay in arm's reach treated the comma as a perfectly normal and unobjectionable character in a filename.
WTF. After 40 years maybe I should have figured that one out.I see Bash only uses commas in Brace expansions:
file{1,2,3}.txt # file1.txt file2.txt file3.txt
I guess it would only be a problem if you want to expand
file,.txt
file,,.txt
file,,,.txt echo file{",",",,",",,,"}.txtNeat trick! I don’t think I’ll namespace everything this way, because there’s some aliases and commands I run so often that the comma would get annoying, but for other less frequently used helper scripts then this will be perfect!
This convention was suggested by the GNU Arch version control system years ago (maybe 20??), but it's really useful for the same tab completion reason and I have kept it for almost two decades, even when I switched to git.
File names or directories starting with a comma where considered “junk”, and ones with a plus sign I think where considered “precious”.
* https://news.ycombinator.com/item?id=40769362 (2024, 169 comments)
* https://news.ycombinator.com/item?id=31846902 (2022, 123 comments)
* https://news.ycombinator.com/item?id=22778988 (2020, 90 comments)
I started using a prefix because I like very short script names that are easy to type
I prefer giving scripts numbers instead of names
Something like "[number"
I use prefixes and suffixes to group related scripts together, e.g., scripts that run other scripts
I have an executable directory like ~/bin but it's not called bin. It contains 100s of short scripts
I find brevity easier to work with. I wish all software was like that
I like the shell (ash not bash). I like assembly language
I have to "adjust" to verbosity, and sometimes I honestly can't, it's paralyzing to the brain, terseness feels more natural
Why not name scripts in natural language like an LLM prompt perhaps (I don't use LLMs so pardon the ignorance), with spaces and punctuation
Bash allows it
echo echo hello > "dear computer, please output the word \"hello\". thank you"
chmod +x "dear computer, please output the word \"hello\". thank you"
"dear computer, please output the word \"hello\". thank you"
That might make sense if I was using the scripts to communicate with a another person, or if I intended other persons besides me to use the scriptsBut neither of those things is true. The scripts are for communicating with a computer and are intended to be used only by me
UNIX allows anyone to rename any file to whatever they want. The UNIX user is free to pursue their own preferences in naming, whatever those may be
> Something like "[number"
> It contains 100s of short scripts
So you call scripts like [1 [2 [3 [4 ... and remember what each one of them does? If yes - that's nuts, I'd visit a doctor.
But I'm not you
A prisoner, new to a particular cell block, was surprised to discover that his fellow inmates passed much of their day by calling out numbers, after which they would laugh heartily for a few moments. Every few minutes an inmate would call out a number and everyone would laugh, and then, after a few moments of silence, someone else would call out a number, and once again laughter. The inmate asked one of the other inmates whom he'd come to know to explain this strange behavior to him.
"It's simple", came the reply. "We know all of our jokes by heart, and there's really no reason to tell them at lenght. Instead, we simply call them out by number."
Though this was strange to him, the new inmate thought he'd join in on the fun. After a few weeks listening to the jokes, he took some initiative and called out "number 27!". But nobody laughed. This seemed very strange to him, since he'd heard others call out that same number, with everyone laughing afterwards. After waiting and waiting, with still no laughter, he finally asked: "why is it that when others call out that joke you laugh, and when I called it, nobody laughed?".
The reply promptly came: "You told it wrong".
I snapshot my entire home directory every hour (using btrfs+snapper), but I exclude ~/.local/ from the snapshots. So I use ~/.local/bin/ for third-party binaries, since there's no reason to back those up; and ~/bin/ for scripts that I wrote myself, since I definitely want to back those up.
This is a pretty idiosyncratic use though, so I'd be surprised if many other people treated both directories this way.
~/.local/bin is tedious to write, when I want to see directory content and - most important - I treat whole ~/.local/ as managed automatically by other services and volatile.
(Anything that modifies standard behaviour is not in PATH, but instead a shell function present only in interactive shells, so as not to break scripts.)
Unix lore: Early unix had two-letter names for most common names to make them easy to type on crappy terminals, but no one* letter command names because the easier were reserved for personal use.
I thought was for mixin externally provided systems like Homebrew, local is for machine or org-level customizations, and ~ is for user-level customizations.
The advantage of /opt is that multi-file software stays together. The disadvantage is that PATHs get long.
~/bin predates it.
And of course you can use both.
It kind of goes against the idea why dotfiles are dot-prefixed.
~/.local was only invented around 2003 and gained widespread usage maybe 15 years or so ago...
People used ~/bin already in the 90s ;-)
I create a home directory "x" for executables that I want to manage as files, and don't want on PATH or as alias.
To run foo: ~/x/foo
For example I have GNU date as ~/x/date so it's independent of the system BSD date.
If we want to stay within (lowercase) alphabetic Latin characters I think prefixing with the least common letters or bigrams that start a word (x, q, y, z, j) is best.
`y' for instance only autocompletes to `yes' and `ypdomainname' on my path.
Choosing a unique bigram is actually quite easy and a fun exercise.
And we can always use uppercase Latin letters since commands very rarely use never mind start with those.
Or prefix files in the local bin dir with a couple letters from your username, like /home/ahepp/.local/bin/ah-mycommand
Also, kudos to keeping it so concise and to the point, thats some prime writing.
I have a command named "decolour", which strips (most) ANSI escape codes. Clear as day what it does, almost nobody uses this spelling when naming commands that later land as part of a distribution.
they're both so easy to reach for
Then again, I don't really believe in performing complex operations manually and directly from a shell, so I don't really understand the use-case for having many small utilities in PATH to begin with.
On my most frequently used machine/dev env this means -
e for vim
m for mise
n for pnpm
c for Claude
x for codex
j for just
I use fish abbreviations for this, as they expand to the full command in the shell history.
But I'm now facing the problem that LLM agents don't like this, and when I instruct them to run certain tools, they remove the leading comma. It's normally fixed with one extra sentence in the prompt, but still inconvenient.
I do something similar with my personal scripts — prefix them with a short namespace. The real win isn't just avoiding collisions though, it's tab completion. Type the prefix and tab, and you immediately see all your custom stuff without wading through hundreds of system commands.
The 2009 date on this is wild. Some of these simple unix conventions age better than most frameworks.
I thought the title meant I should type ,ls instead of ls.
Like so?
quick, easy and consistent. entirely voluntary.
Bravo
However, I'd advocate for an ë key then.
Off-topic: What the hell is that font on this website? And why does the "a" look like that?
It went weird pretty quickly...
dude, whois my.carIt is objectively cleaner to keep your user scripts in your home, that way they are only in _your_ PATH, whereas putting them in /usr/[local/]bin implicitly adds them to every [service] user on the machine, which I can see creating obscure undesired effets.
Not even mentioning the potential issues with packages that could override your scripts at install, unexpected shadowing of service binaries, setuid security implications, etc.
2024: https://news.ycombinator.com/item?id=40769362
`.local/bin` seems to be much more common in my experience for this use case. And for good reason.
I'd prefer to use underscore (when writing BASH scripts, I name all my local variables starting with underscore), but a simple two or three letter prefix would also work. I don't like the idea of a punctuation prefix as punctuation usually has a specific meaning somewhere and including it as the first character in a filename looks wrong. (e.g. Comma is typically used as a list separator and it's a bit of cognitive dissonance to see it not used in that context)
> I don't like the idea of a punctuation prefix as punctuation usually has a specific meaning somewhere and including it as the first character in a filename looks wrong.
So you don’t use dotfiles? ;)
I'm not convinced by "quicker to type" arguments as that's rarely the bottleneck, so I'm perfectly happy with using underscores in filenames and variables. I wouldn't use underscore as the beginning character of a filename unless it had a specific meaning to me (e.g. temporary files), so I'd be more inclined to use a two or three character prefix instead.
I would replace underscore with “-“ or “.”
Nowadays, I tend to skip using a personal prefix and just try to name commands with a suitable verb in front (e.g. "backupMySQL") and ensure that there's no name collisions.