How to Run a Bash Script From Anywhere on Your Apple Computer
Once I started blogging I found I needed to compress my images, so they’d be smaller, before uploading them to my website. Over time I realized this could be done so much quicker if I put each single blog post’s images into its own directory (folder) and run a Bash script to compress all of the current images at once. This was amazing but quickly became cumbersome once again when I realized I had two options; either to move my script to each new directory of images each time I ran it or run the script from one place and pass in the location of the images as an argument. I had gone with the first option which meant I had to navigate to where I want the script to be, move it, and run it… then move it into another directory and run it again… and again and again. This was much too troublesome; I wanted it streamlined so I could run my script from anywhere freeing me to compress and, later, watermark my images at will.
Quick Aside: Don’t Forget to Make Your Script Executable
When I first started working with bash scripts I saw, by default, they could only be run by using the sh
or bash
command unless I first made them executable, using chmod
, in which case I could run them by simply calling their filename. I mentioned this as below I use executable files and sh
isn’t used.
Additionally, I use iTerm2 (Build 3.4.19) so my terminal may look different from yours.
So How Can I Run a Script From Anywhere?
When you run a command in your terminal the computer looks through a list of directories where the file might reside and, if found, executes it. This special list of directories is stored in the PATH
environment variable on your computer. If your shell goes through all of these paths, in order, and can’t find the file it then lets you know that it can’t be found before exiting. If there are two matching files it will run whichever one it comes across first.
To show this I opened my terminal and ran one of the scripts I had previously created and put into a directory whose path was included in my PATH
variable. As such it was found and executed. For contrast I next ran a file that doesn’t exist so this time the terminal returned fish: Unknown command: thisIsNotInPath
. Here’s a screenshot of these two attempts:
To see what pathways are in your environment variable you just need to open your terminal and type in echo $PATH
. The echo command prints out the arguments as standard output thus showing it to you the user. The $
signifies a variable and PATH
is this variable name. When I run this command on my computer I get:
❯ echo $PATH
/Users/kyra/bin /usr/local/bin
/System/Cryptexes/App/usr/bin /usr/bin /bin
/usr/sbin /sbin /Library/Apple/usr/bin
This means when I execute a command my computer looks through these eight different directories (specifically /Users/kyra/bin
, /usr/local/bin
, /System/Cryptexes/App/usr/bin
, /usr/bin
, /bin
, /usr/sbin
, /sbin
, and /Library/Apple/usr/bin
) one by one to find that file. It does this in order so if you have two files with the same name it will always execute the first one it comes across each time.
The directory I manually added to the PATH
and use is /Users/kyra/bin
. This is the directory where I put all of the scripts I want to run from anywhere. So for the below example I created a new file named testing.sh
and, for it’s contents, had it say echo "Hello Readers"
so when the file is eventually executed I’ll see Hello Readers
outputted. Here it is:
// First I called a file that doesn't exist to
// show you the output
❯ neverCreated.sh
fish: Unknown command: neverCreated.sh
// I then call the script I just created. It
// prints out, echoes, the words "Hello Readers".
// At this time the script isn't an executable
// but it can be found
❯ testing.sh
fish: Unknown command. '/Users/kyra/bin/testing.sh'
exists but is not an executable file.
// If I start with sh it works great and outputs
// the message.
❯ sh testing.sh
Hello Readers
// I can easily chmod it and make it executable
// so now...
❯ chmod u+x bin/testing.sh
// I can now call it and it works.
❯ testing.sh
Hello Readers
// To test this I navigate elsewhere on my
// computer....
❯ cd Desktop/temp/
// And call it again. It works as my computer
// looked through my paths and found it in the
// bin directory.
~/Desktop/temp
❯ testing.sh
Hello Readers
And with that you can run your script from anywhere on your computer and you don’t need to navigate into the bin
directory at all!
How Is This Useful?
In my case I create a new directory each time I start planning a new blog post idea. I start by adding all the images I may want to use in my post, go through them individually to remove any excess ones, crop the remaining ones, manually create a Pinterest and Instagram image on Canva, and then compress these final remaining ones. To compress them I now only need to open a terminal to this directory and execute my script to have all my compressed images ready to upload. Before putting my script in the bin
directory the compressing step took longer and was way more cumbersome than I wanted… now it’s simply a quick footnote to my process. If you’re more interested in the script itself than how I execute it you can read about it in my previous post Use a Simple Bash Script to Resize Your Images Quickly and Easily or you can check it out on GitHub.
I also used this technique for my vim
and nvim
. Right now when I want to edit a file in the terminal I type vim filename.extension
but in reality I have a symbolic link in my bin
directory (where it can be found and so executed from anywhere) that links to /usr/local/bin/nvim
and runs neoVim
instead. It’s a simple trick so I could continue using muscle memory to open my editor without needing to type something else in.
Less personally but more pivotal is the way your computer already uses this. If you navigate to all those pathways listed in your $PATH
you’ll see all the files and programs that can be found and executed from anywhere. One quick example is vim
, python
, or a myriad of all the programs you can run from anywhere in the terminal. Using Python
, for instance, you can run your py
files by typing in python filename.p
y and your computer will know to run the filename using python.
These are the main reasons I personally use this but I’d love to know more ways that work for you so feel free to share in the comments at the bottom.
But don’t just take my word for this. If you browse through all the directories listed under your $PATH variable you can see all the programs and files that also use this.
What if I Want My Script to Live Somewhere Else but Still Be Called From Anywhere?
If you want your script to live somewhere outside the list of directories in your PATH
variable you could always create a symbolic link so it thinks it’s in one of those directories. In my case my computer sees two of my scripts in my bin/
directory yet only one technical resides there. The other one actually lives in my Development/scripts/
directory but is symbolically linked so the computer sees it in the bin/
directory. When I navigate to the bin/
directory and list the contents, using -l
to show the long list (link to StackExchange for more information), I see this:
~/bin
❯ ls -la
total 8
drwxr-xr-x 5 kyra staff 160 Jan 19 11:37 ./
drwxr-xr-x+ 32 kyra staff 1024 Jan 19 11:07 ../
lrwxr-xr-x 1 kyra staff 59 Oct 12 2021 compress_my_images.sh@ -> /Users/kyra/Development/scripts/compress_my_images.sh
-rwxr-xr-x 1 kyra staff 1504 Jan 11 10:24 createAppIcons.sh*
lrwxr-xr-x 1 kyra staff 19 Oct 12 2021 vim@ -> /usr/local/bin/nvim
Here the createAppIcons.sh
file lives in the bin/
directory while my compress_my_images.sh
script and vim
call doesn’t. The shell can see it in the bin
directory and follow it to where it really lives (after the arrow).
I talked about creating symbolic links before in my reMarkable template post under Simplify the Process With a Symbolic Link. Essentially, to create the symbolic link I open the terminal and use the link command, ln
, followed by the -s
parameter to specify that I want it to be a symbolic link. I next follow it with the target pathway, where the linked file is located, and then follow that with the location and filename I want to use for that link. So, for instance, in this below example I create an empty script on my Desktop/temp/
directory and then link it to the bin
directory. I can confirm it was created using the ls
command and passing in the l
parameter to get more information. Here’s what I did:
// Use the touch command to create an
// empty script in my temp directory
// on my Desktop
❯ touch ~/Desktop/temp/testingThis.sh
// Create the symbolic link making sure
// to pass in the target file we just
// created followed by what I want it
// to be called in the bin directory.
❯ ln -s ~/Desktop/temp/testingThis.sh bin/testingHere.sh
// To confirm this worked can run ls -l
// to see all the files and, if symbolic
// linking, what they link to.
❯ ls -l bin/
total 8
lrwxr-xr-x 1 kyra staff 59 Oct 12 2021 compress_my_images.sh@ -> /Users/kyra/Development/myBashScripts/compress_my_images.sh
-rwxr-xr-x 1 kyra staff 1504 Jan 11 10:24 createAppIcons.sh*
lrwxr-xr-x 1 kyra staff 39 Jan 19 15:50 testingHere.sh@ -> /Users/kyra/Desktop/temp/testingThis.sh
lrwxr-xr-x 1 kyra staff 19 Oct 12 2021 vim@ -> /usr/local/bin/nvim
But what if you have a whole directory of scripts you want to run from anywhere and don’t want to move or symbolically link them to a directory in the $PATH
list…
What if I Want to Add to $Path’s List of Directories?
Back when I was getting my compression script to run from anywhere I decided to add my bin/
directory to the list of directories in my $PATH
. I accomplished this by editing my config.fish
file in my .config/fish/
directory (as I’m using the Fish
terminal emulator) by doing:
vim ~/.config/fish/config.fish
Then at the bottom of my file I added one line of text:
set PATH $HOME/bin $PATH
And this is how I added my bin/
directory to be recognized.
I haven’t had to do this in any other way so in case you don’t use fish
or want a different method I figured I’d share some other links that look simple to follow for either adding a directory to PATH
temporarily, in the current session, or more permanently. These are TechRepublic: Linux 101: What is the Linux $PATH?, How-To Geek: How to Add a Directory to Your $PATH in Linux, PhoenixNAP: Linux: Add a Directory to PATH, or My Cyber Universe: Create a personal bin directory (~/bin) and run scripts without specifying their full path.
Some More Information…
Looking into this more Warp gives great information if you’re more interested in how the terminal works and, specifically, what happens when you open a terminal and enter ‘ls’. Their linked Bash Aliases was also interesting. SysAdvent’s down the rabbit hole with ‘ls’ seems more in depth although I haven’t spent as much time reading but sharing so I can also go back to it in the future.
I hope this was able to help you move your script, or other file, so it was recognizable by your computer. If it helped I’d love to hear how in the comments below. If I missed something you think would be helpful feel free to share that too. I’m sure it will help someone.
Hope you’re having a great day!
If you’re interested in getting any of my future blog updates I currently share them to my Facebook page and Instagram account. You’re also more than welcome to join my email list located right under the search bar or underneath this post.