Passing Files by Absolute Filepath
The simplest way to pass a file to a terminal command is by its absolute filepath. This isn't very common because it's inconvenient. For example: (below $ represents the shell prompt)
$ /bin/cat /home/john/Downloads/Secrets.txt
The penguin's name is Tux
Above, we're using the /bin/cat program to print the contents of the /home/John/Downloads/Secrets.txt. in the terminal. This would take too long to type, normally, but hopefully there are two methods we can use to speed this up.
First, we can use the autocomplete feature by pressing the Tab key.
Second, we can simply copy the file in a file manager with Ctrl+C, and then paste it in the terminal. Doing so will paste its filepath.
Referring to the Home Directory
In Bash, we can refer to the home directory by starting an argument with a tilde (~) in the command-line. The argument will automatically be replaced by your home directory before the terminal command gets the argument list from the shell. For example, if the home directory is /home/john:
$ /bin/echo ~
/home/john
$ /bin/echo ~/Pictures
/home/john/Pictures
$ /bin/echo "~"
~
$ /bin/echo \~
~
As we can see above, /bin/echo prints "~" when we surround it by quotes or escape it using a backslash (\), but if we don't do that, Bash will replace ~ by /home/john before that argument gets to /bin/echo.
Passing Files by Filename
We can refer to a file in the current working directory by its filename if it's not the zeroth argument of the command-line.
~$ /bin/ls Pictures
Vacation
Wallpapers
Above, we used /bin/ls to list the files and folders inside the /home/<your username>/Pictures directory. This worked because the current working directory was /home/<your username>.
Passing Commands by Filename
The zeroth argument (the command name) may be just its filename if it resides in default binaries directories like /bin and /usr/bin.
~$ ls Pictures
Vacation
Wallpapers
Above, ls refers to /bin/ls because there is an ls in /bin/ls.
Note: exceptionally, although a /bin/pwd program exists, if you type pwd in Bash what is executed is a built-in shell command and not the /bin/pwd program.
The directories searched by Bash are written in the $PATH environment variable, separated by colon (:). It may look like this:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
You can add your own custom directories to this with PATH=$PATH:/custom/directory.
Passing an Executable in the Current Working Directory as the Zeroth Argument
To pass a filename relative to the current working directory as the zeroth argument, we must prefix it with ./. In this text code, the dot (.) represents the current directory. For example:
~$ echo "echo Hello world!" > hello-world.sh
~$ chmod +x hello-world.sh
~$ ./hello-world.sh
Hello world!
Above, we create a file called hello-world.sh in the current working directory with the contents "echo Hello world!". This is a shell script file. We make the file executable using chmod +x [RWX File Permissions Notation on Linux]. Then we execute it with ./hello-world.sh.
If we typed just hello-world.sh, it wouldn't work because Bash will search /bin for a hello-world.sh and won't find it there.
~$ hello-world.sh
hello-world.sh: command not found
Filenames that Start with Dashes
By convention, optional arguments start with a dash (-), and, consequently, terminal commands will assume anything that starts as a dash is an optional argument, not a positional argument, which makes it a bit complicated to pass a filename that starts with a dash to a program.
~$ touch -foo
touch: invalid option -- 'o'
Try 'touch --help' for more information.
~$ touch --foo
touch: unrecognized option '--foo'
Try 'touch --help' for more information.
Above, we're trying to create an empty file called -foo or --foo in the current working directory, but it isn't working because touch thinks we're trying to pass an option that it doesn't support.
We can solve this by using ./ before the filename.
~$ touch ./-foo
~$ touch ./--foo
~$ ls | grep foo
-foo
--foo
Above, we used | grep foo to filter the output of ls to entries that contained the text "foo," ignoring things like Pictures. Observe that we did create both -foo and --foo as we wanted.
Passing Files by Relative Filepath
Files in Subdirectories
We can refer to files in subdirectories in the current working directory by using a forward slash (/) to separate the subdirectory. For example:
~$ ls Pictures/Vacation
'Summer 2023'
'Winter 2024'
Above, we use ls with ~/Pictures/Vacation.
Parent Directory
We can refer to the parent directory of the current working directory with two dots (..).
~$ cd Pictures/Vacation
~/Pictures/Vacation$ cd ..
~/Pictures$ cd ..
~$
Above, we visit ~/Pictures/Vacation using cd, and then we move up twice, going back to where we started.
The two dots (..) always mean "parent directory" while a single dot (.) means "same directory." For example:
~$ cd Pictures/Vacation
~/Pictures/Vacation$ cd ../..
~$
Above ../.. means the parent directory of the parent directory. Meanwhile, a single dot (.) doesn't do anything if we repeat it multiple times:
~$ cd ./././././././././././Pictures
~/Pictures$ cd ./../.
~/$
Essentially, path and path/. refer to the same path, so we can keep adding dots /./././. and the result will still be the same.
By the way, three dots (...) don't have any special meaning. It's only a single dot or two dots that you have to worry about.
Files from Parent Directory
We can descend from the parent directory after ascending to it:
~$ cd Pictures
~/Pictures$ cd ../Downloads
~/Downloads$
Above, we used ../Downloads to change the current working directory to a different folder in the same parent directory as the current working directory, i.e. to a sibling folder.
Passing the Current Working Directory as Argument
It's sometimes useful to refer to the current directory itself as an argument. We can do this with a dot (.).
~$ cd Videos
~/Videos$ mv ../video.mp4 .
Above, we're moving a file called video.mp4 that exists in the parent directory to the current directory (.). It's a bit hard to see, but after mp4 we have a dot (.) indicating that the current directory is the target of the mv command.
Passing Files by Globbing
In Bash, if an argument starts with an asterisk (*), it will be replaced by multiple arguments referring to all files in the current working directory except dotfiles. These files will be in random order. Some programs can make use of this behavior because when they have more than 2 positional arguments they use the first or last argument specially. For example:
~/tasks$ mv done-today/* done
The code above will create an argument list that looks like mv done-today/task-1 done-today/task-2 done. The command mv will move all tasks from done-today to done because it uses the last argument as target directory.
This also works with many other basic shell utilities.
We can use a suffix after the asterisk to tell Bash to filter by file extension, for example.
~/unzipped$ cp *.jpg photos
Above, we're copying all files with a .jpg extension in unzipped to unzipped/photos. Bash also lets use use multiple possible values in a glob using curly braces ({}) and commas (,).
If you aren't sure about what a glob will do, keep in mind that all it does is create a list of arguments, so you can use echo to see what the argument list will look like before executing it.
~/unzipped$ echo *{.png,.jpg,.webp}
foo.png foo-2.png foo-3.jpg *.webp
Above, we find an unexpected effect of globbing. The folder didn't have any WebP files, so Bash added *.webp as an argument. I honestly don't know why it does that.
The asterisk can be escaped as \*, but you also need to escape the curly braces then. Surrounding it by quotes also works.
$ echo \*{.png,.jpg,.webp}
*.jpg *.png *.webp
$ echo \*\{.png,.jpg,.webp\}
*{.png,.jpg,.webp}
$ echo "*{.png,.jpg,.webp}"
*{.png,.jpg,.webp}