Run It from Anywhere: ~/bin on Your PATH

Copy a finished script into ~/bin without its .sh, put ~/bin on your PATH, and it runs by name from any folder like any other command, as long as the paths inside it work from anywhere and its name isn't already taken, which type checks.

Apprentice · Unix · Linux · Updated

After this lesson you can

  • Put your own scripts in ~/bin on your PATH so they run by name from any folder, with paths inside them that work from anywhere
  • Choose a script name that doesn't clash with an existing command, checking it with type, and drop the .sh

Key takeaways

  • A script runs in the folder you're in, not the folder it's saved in, so a path like todo.txt breaks elsewhere. Write ~/terminal-practice/morning/todo.txt instead.
  • ~/bin is the usual folder for your own commands. Copy a script there without its .sh, and on your PATH it runs by name from anywhere.
  • Ubuntu's ~/.profile adds ~/bin to PATH in every new terminal once the folder exists. On a Mac, add the line to ~/.zshrc.
  • Before you use a name, type checks it isn't taken. A script called test never runs, because the shell's own test comes first.

Your morning script works, but only from its own folder, and typing ~/terminal-practice/morning/morning.sh every morning is no shortcut. You want to open a terminal, type morning, and have it run, the way ls and date do. That's what turns a script into a command of your own, and it's how developers and administrators keep the dozens of small tools they rely on a word away.

A script runs where you are

Start from your home folder and run the script by its full path:

cd ~
~/terminal-practice/morning/morning.sh
Good morning, sam!
Today is Monday, November 09.

To do:
cat: todo.txt: No such file or directory

Free disk space:
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdd       1007G   31G  926G   4% /

Everything worked except the list. A script runs in the folder you're in, not the folder it's saved in, so cat todo.txt looked for todo.txt in your home folder. It's like leaving a note that says "the key is in the drawer" without saying which room.

The fix is a path that works from anywhere: one that starts with ~ (Moving Around: cd, .. and ~). Open the script in nano and change the cat line to this:

cat ~/terminal-practice/morning/todo.txt

Run it again from your home folder, and the list is back:

~/terminal-practice/morning/morning.sh | head -n 7
Good morning, sam!
Today is Monday, November 09.

To do:
pay the phone bill
finish the history essay
buy coffee beans

~/bin: a folder for your own commands

PATH: How a Command Is Found showed that the shell finds a command by searching the folders on your PATH. So give your scripts a folder of their own and put it on the list. The usual name is ~/bin, read "home bin", short for binaries, the old word for programs:

mkdir -p ~/bin
cp ~/terminal-practice/morning/morning.sh ~/bin/morning
ls -l ~/bin
total 4
-rwxr-xr-x 1 sam sam 290 Nov  9 06:09 morning

The copy is called morning, without .sh. Nobody types ls.sh: a command's name is what you type, and the ending only matters to editors. cp kept the x, so it may run. Now put ~/bin on your PATH and try it from anywhere:

export PATH="$HOME/bin:$PATH"
which morning
cd /tmp
morning | head -n 3
/home/sam/bin/morning
Good morning, sam!
Today is Monday, November 09.

morning, from any folder, by name. When you change morning.sh later, copy it into ~/bin again, or link it instead with ln -s (Shortcuts to Files: ln -s) so ~/bin always runs the latest.

Keep it on your PATH

The export line lasts until you close the window. On Ubuntu you don't need to add it anywhere: Ubuntu's own ~/.profile already has these lines, which put ~/bin at the front of PATH whenever the folder exists:

grep -A 2 'HOME/bin"' ~/.profile
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

~/.profile runs when you sign in. In WSL, every new Ubuntu tab signs in, so open a new tab and morning works there. On Ubuntu's desktop, sign out and back in once.

On a Mac, zsh doesn't read ~/.profile. Back up ~/.zshrc, add this line at the end, and open a new window (Startup Files: .bashrc, .zshrc and source):

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

Names that don't clash

~/bin comes first on PATH, so a script there wins over a program with the same name. But aliases, functions and the shell's own builtins come before PATH altogether. Before you name a script, ask type what the name already means:

type test
type morning
type evening
test is a shell builtin
morning is /home/sam/bin/morning
bash: type: evening: not found

not found means the name is free. test is taken, by a shell builtin, and this is what happens to a script with that name:

cp ~/bin/morning ~/bin/test
test
type -a test
rm ~/bin/test
test is a shell builtin
test is /home/sam/bin/test
test is /usr/bin/test
test is /bin/test

test printed nothing: bash ran its own builtin, and your script never started. type -a lists every test in the order the shell checks them, and yours isn't first. A few habits that avoid this:

  • Check with type first, and pick a name that says not found.
  • Use lowercase and hyphens, such as morning or backup-photos, never spaces.
  • Drop the .sh on the copy in ~/bin, and keep it on the file you edit.

Check yourself

2 questions to try on the lesson page.

So what

A folder on your PATH, a name nobody else uses, and paths that work from anywhere: your script is now a command, as much a part of your computer as ls.

Key terms

Cheat sheets

Related