To check if a directory exists in a shell script, you can use the following:
if [ -d "$DIRECTORY" ]; then
# Control will enter here if $DIRECTORY exists.
fi
Or to check if a directory doesn't exist:
if [ ! -d "$DIRECTORY" ]; then
# Control will enter here if $DIRECTORY doesn't exist.
fi
However, as Jon Ericson points out, subsequent commands may not work as intended if you do not take into account that a symbolic link to a directory will also pass this check.
E.g. running this:
ln -s "$ACTUAL_DIR" "$SYMLINK"
if [ -d "$SYMLINK" ]; then
rmdir "$SYMLINK"
fi
Will produce the error message:
rmdir: failed to remove `symlink': Not a directory
So symbolic links may have to be treated differently, if subsequent commands expect directories:
if [ -d "$LINK_OR_DIR" ]; then
if [ -L "$LINK_OR_DIR" ]; then
# It is a symlink!
# Symbolic link specific commands go here.
rm "$LINK_OR_DIR"
else
# It's a directory!
# Directory command goes here.
rmdir "$LINK_OR_DIR"
fi
fi
Take particular note of the double-quotes used to wrap the variables. The reason for this is explained by 8jean in another answer.
If the variables contain spaces or other unusual characters it will probably cause the script to fail.
#!/usr/bin/env bash
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
is a useful one-liner which will give you the full directory name of the script no matter where it is being called from.
It will work as long as the last component of the path used to find the script is not a symlink (directory links are OK). If you also want to resolve any links to the script itself, you need a multi-line solution:
#!/usr/bin/env bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
This last one will work with any combination of aliases, source
, bash -c
, symlinks, etc.
Beware: if you cd
to a different directory before running this snippet, the result may be incorrect!
Also, watch out for $CDPATH
gotchas, and stderr output side effects if the user has smartly overridden cd to redirect output to stderr instead (including escape sequences, such as when calling update_terminal_cwd >&2
on Mac). Adding >/dev/null 2>&1
at the end of your cd
command will take care of both possibilities.
To understand how it works, try running this more verbose form:
#!/usr/bin/env bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
TARGET="$(readlink "$SOURCE")"
if [[ $TARGET == /* ]]; then
echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
SOURCE="$TARGET"
else
DIR="$( dirname "$SOURCE" )"
echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"
And it will print something like:
SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
Best Answer
To run a non-executable
sh
script, use:To run a non-executable
bash
script, use:To start an executable (which is any file with executable permission); you just specify it by its path:
To make a script executable, give it the necessary permission:
When a file is executable, the kernel is responsible for figuring out how to execte it. For non-binaries, this is done by looking at the first line of the file. It should contain a
hashbang
:The hashbang tells the kernel what program to run (in this case the command
/usr/bin/env
is ran with the argumentbash
). Then, the script is passed to the program (as second argument) along with all the arguments you gave the script as subsequent arguments.That means every script that is executable should have a hashbang. If it doesn't, you're not telling the kernel what it is, and therefore the kernel doesn't know what program to use to interprete it. It could be
bash
,perl
,python
,sh
, or something else. (In reality, the kernel will often use the user's default shell to interprete the file, which is very dangerous because it might not be the right interpreter at all or it might be able to parse some of it but with subtle behavioural differences such as is the case betweensh
andbash
).A note on
/usr/bin/env
Most commonly, you'll see hash bangs like so:
The result is that the kernel will run the program
/bin/bash
to interpret the script. Unfortunately,bash
is not always shipped by default, and it is not always available in/bin
. While on Linux machines it usually is, there are a range of other POSIX machines wherebash
ships in various locations, such as/usr/xpg/bin/bash
or/usr/local/bin/bash
.To write a portable bash script, we can therefore not rely on hard-coding the location of the
bash
program. POSIX already has a mechanism for dealing with that:PATH
. The idea is that you install your programs in one of the directories that are inPATH
and the system should be able to find your program when you want to run it by name.Sadly, you cannot just do this:
The kernel won't (some might) do a
PATH
search for you. There is a program that can do aPATH
search for you, though, it's calledenv
. Luckily, nearly all systems have anenv
program installed in/usr/bin
. So we startenv
using a hardcoded path, which then does aPATH
search forbash
and runs it so that it can interpret your script:This approach has one downside: According to POSIX, the hashbang can have one argument. In this case, we use
bash
as the argument to theenv
program. That means we have no space left to pass arguments tobash
. So there's no way to convert something like#!/bin/bash -exu
to this scheme. You'll have to putset -exu
after the hashbang instead.This approach also has another advantage: Some systems may ship with a
/bin/bash
, but the user may not like it, may find it's buggy or outdated, and may have installed his ownbash
somewhere else. This is often the case on OS X (Macs) where Apple ships an outdated/bin/bash
and users install an up-to-date/usr/local/bin/bash
using something like Homebrew. When you use theenv
approach which does aPATH
search, you take the user's preference into account and use his preferred bash over the one his system shipped with.