Bash syntax error: unexpected end of file - bash

Forgive me for this is a very simple script in Bash. Here's the code:
#!/bin/bash
# june 2011
if [ $# -lt 3 -o $# -gt 3 ]; then
echo "Error... Usage: $0 host database username"
exit 0
fi
after running sh file.sh:
syntax error: unexpected end of file

I think file.sh is with CRLF line terminators.
run
dos2unix file.sh
then the problem will be fixed.
You can install dos2unix in ubuntu with this:
sudo apt-get install dos2unix

Another thing to check (just occured to me):
terminate bodies of single-line functions with semicolon
I.e. this innocent-looking snippet will cause the same error:
die () { test -n "$#" && echo "$#"; exit 1 }
To make the dumb parser happy:
die () { test -n "$#" && echo "$#"; exit 1; }

i also just got this error message by using the wrong syntax in an if clause
else if (syntax error: unexpected end of file)
elif (correct syntax)
i debugged it by commenting bits out until it worked

an un-closed if => fi clause will raise this as well
tip: use trap to debug, if your script is huge...
e.g.
set -x
trap read debug

I got this answer from this similar problem on StackOverflow
Open the file in Vim and try
:set fileformat=unix
Convert eh line endings to unix endings and see if that solves the
issue. If editing in Vim, enter the command :set fileformat=unix and
save the file. Several other editors have the ability to convert line
endings, such as Notepad++ or Atom
Thanks #lemongrassnginger

This was happening for me when I was trying to call a function using parens, e.g.
run() {
echo hello
}
run()
should be:
run() {
echo hello
}
run

I had the problem when I wrote "if - fi" statement in one line:
if [ -f ~/.git-completion.bash ]; then . ~/.git-completion.bash fi
Write multiline solved my problem:
if [ -f ~/.git-completion.bash ]; then
. ~/.git-completion.bash
fi

So I found this post and the answers did not help me but i was able to figure out why it gave me the error. I had a
cat > temp.txt < EOF
some content
EOF
The issue was that i copied the above code to be in a function and inadvertently tabbed the code. Need to make sure the last EOF is not tabbed.

on cygwin I needed:-
export SHELLOPTS
set -o igncr
in .bash_profile . This way I didn't need to run unix2dos

FOR WINDOWS:
In my case, I was working on Windows OS and I got the same error while running autoconf.
I simply open configure.ac file with my NOTEPAD++ IDE.
Then I converted the File with EOL conversion into Windows (CR LF) as follows:
EDIT -> EOL CONVERSION -> WINDOWS (CR LF)

Missing a closing brace on a function definition will cause this error as I just discovered.
function whoIsAnIidiot() {
echo "you are for forgetting the closing brace just below this line !"
Which of course should be like this...
function whoIsAnIidiot() {
echo "not you for sure"
}

I was able to cut and paste your code into a file and it ran correctly. If you
execute it like this it should work:
Your "file.sh":
#!/bin/bash
# june 2011
if [ $# -lt 3 -o $# -gt 3 ]; then
echo "Error... Usage: $0 host database username"
exit 0
fi
The command:
$ ./file.sh arg1 arg2 arg3
Note that "file.sh" must be executable:
$ chmod +x file.sh
You may be getting that error b/c of how you're doing input (w/ a pipe, carrot,
etc.). You could also try splitting the condition into two:
if [ $# -lt 3 ] || [ $# -gt 3 ]; then
echo "Error... Usage: $0 host database username"
exit 0
fi
Or, since you're using bash, you could use built-in syntax:
if [[ $# -lt 3 || $# -gt 3 ]]; then
echo "Error... Usage: $0 host database username"
exit 0
fi
And, finally, you could of course just check if 3 arguments were given (clean,
maintains POSIX shell compatibility):
if [ $# -ne 3 ]; then
echo "Error... Usage: $0 host database username"
exit 0
fi

In my case, there is a redundant \ in the like following:
function foo() {
python tools/run_net.py \
--cfg configs/Kinetics/X3D_8x8_R50.yaml \
NUM_GPUS 1 \
TRAIN.BATCH_SIZE 8 \
SOLVER.BASE_LR 0.0125 \
DATA.PATH_TO_DATA_DIR ./afs/kinetics400 \
DATA.PATH_PREFIX ./afs/kinetics400 \ # Error
}
There is NOT a \ at the end of DATA.PATH_PREFIX ./afs/kinetics400

I just cut-and-pasted your example into a file; it ran fine under bash. I don't see any problems with it.
For good measure you may want to ensure it ends with a newline, though bash shouldn't care. (It runs for me both with and without the final newline.)
You'll sometimes see strange errors if you've accidentally embedded a control character in the file. Since it's a short script, try creating a new script by pasting it from your question here on StackOverflow, or by simply re-typing it.
What version of bash are you using? (bash --version)
Good luck!

Make sure the name of the directory in which the .sh file is present does not have a space character. e.g: Say if it is in a folder called 'New Folder', you're bound to come across the error that you've cited. Instead just name it as 'New_Folder'. I hope this helps.

Apparently, some versions of the shell can also emit this message when the final line of your script lacks a newline.

In Ubuntu:
$ gedit ~/.profile
Then, File -> Save as and set end line to Unix/Linux

I know I am too late to the party. Hope this may help someone.
Check your .bashrc file. Perhaps rename or move it.
Discussion here: Unable to source a simple bash script

For people using MacOS:
If you received a file with Windows format and wanted to run on MacOS and seeing this error, run these commands.
brew install dos2unix
sh <file.sh>

If the the script itself is valid and there are no syntax errors, then some possible causes could be:
Invalid end-of-lines (for example, \r\n instead of \n)
Presence of the byte order mark (BOM) at the beginning of the file
Both can be fixed using vim or vi.
To fix line endings open the file in vim and from the command mode type:
:set ff=unix
To remove the BOM use:
:set nobomb

For those who don't have dos2unix installed (and don't want to install it):
Remove trailing \r character that causes this error:
sed -i 's/\r$//' filename
Details from this StackOverflow answer. This was really helpful.
https://stackoverflow.com/a/32912867/7286223

Related

Can nano detect file type without extension by the shebang to have proper syntax highlighting?

I see nano cannot detect a file type by a shebang (hashbang) line like
#!/usr/bin/env bash
or similar.
Vim copes with this task w/o problems.
Is there a way to make it work for nano?
P.S. Created github issue.
P.P.S. Even nano 4.2 version doesn't support this. (compiled from sources on CentOS7)
There is a bug in nano syntax highlighting detection for .sh files, which I have found present in nano 4.8 and NOT present in nano 2.9.8, whereby a #! line with /env in it will not detect any shell except sh.
I have even found the specific commit which fixed it: https://git.savannah.gnu.org/cgit/nano.git/commit/?id=6a3ba2ab501c138c7ee1e72d2a65cea77342a43c
Annoyingly, at time of writing this is affecting .sh color syntax highlighting in up-to-date nano from the package manager on up-to-date current LTS version of Ubuntu (20.04).
To fix it, you have to replace your /usr/share/nano/sh.nanorc with the same file from a newer (or older!) version of nano.
The current one from https://git.savannah.gnu.org/cgit/nano.git/tree/syntax/sh.nanorc works fine.
I've decided to make simple wrapper for that.
#!/usr/bin/env bash
####################################################
# Find file type and set syntax highlight for nano #
####################################################
set -o pipefail
set -o errexit
set -o nounset
#set -o xtrace
# Determine path to nano binary file
if [[ -f /usr/local/bin/nano ]]; then
nano_bin=/usr/local/bin/nano
elif [[ -f /usr/bin/nano ]]; then
nano_bin=/usr/bin/nano
else
echo 'error: Sorry, nano binary file not found neither by path /usr/local/bin/nano nor /usr/bin/nano.' > /dev/stderr
exit 2
fi
# check if syntax highlight argument already passed
if ! echo ${#} | grep -E '(-Y|--syntax)' > /dev/null; then
# fetch interpreter name
syntax_type=$(head -1 bin/cli | grep '#!' | awk '{match($0,"([a-z]+)$",a)}END{print a[0]}')
if [[ -n "${syntax_type}" ]]; then
# map a file interpreter onto syntax type like BASH into SH
case "${syntax_type}" in
bash)
syntax_type=sh
;;
esac
nano_argument="--syntax=${syntax_type}"
fi
fi
${nano_bin} ${nano_argument:-} ${#}
Installation
Simple option for bash
Copy the code into ~/.nano-wrap.sh
nano ~/.nano-wrap.sh
Add an alias into your .bashrc file:
echo 'alias nano="bash ~/.nano-wrap.sh" >> ~/.bashrc'
And reload it:
source ~/.bashrc
J
I've found that saving the file (Ctrl+O, type name, Enter) will cause nano to auto-detect the file type from the shebang and then syntax-highlight the file appropriately from then on.

Bash script not working on a new dedicated server

Recently I have migrated to the new dedicated server which is running on the same operating system - FreeBSD 8.2. I got a root account access and all permissions have been set properly.
My problem is that, the bash script I was running on the old server doesn't works on the new machine, the only error appearing while running the script is:
# sh script.sh
script.sh: 3: Syntax error: word unexpected (expecting ")")
Here is the code itself:
#!/usr/local/bin/bash
PORTS=(7777:GAME 11000:AUTH 12000:DB)
MESSG=""
for i in ${PORTS[#]} ; do
PORT=${i%%:*}
DESC=${i##*:}
CHECK=`sockstat -4 -l | grep :$PORT | awk '{print $3}' | head -1`
if [ "$CHECK" -gt 1 ]; then
echo $DESC[$PORT] "is up ..." $CHECK
else
MESSG=$MESSG"$DESC[$PORT] wylaczony...\n"
if [ "$DESC" == "AUTH" ]; then
MESSG=$MESSG"AUTH is down...\n"
fi
if [ "$DESC" == "GAME" ]; then
MESSG=$MESSG"GAME is down...\n"
fi
if [ "$DESC" == "DB" ]; then
MESSG=$MESSG"DB is down...\n"
fi
fi
done
if [ -n "$MESSG" ]; then
echo -e "Some problems ocurred:\n\n"$MESSG | mail -s "Problems" yet#another.com
fi
I don't really code in bash, so I don't know why this happend...
Bourne shell (sh) doesn't support arrays, that's why you're running into this error when you use
sh script.sh
Use bash instead
bash script.sh
Note: I suspect that sh script.sh worked on the old server because sh is linked to bash there.
also you shouldn't need to run it through sh (that's what the
#!
on the first line is for - the OS will run the remainder of the line as a command and pass the contents of the file for it to interpret). Just make the script executable:
chmod +x script.sh
and then you can just run it directly without the sh in front of the name.
It's possible that the default shell is not bash and so by running it through sh you're interpreting it with a different shell which is then giving the error
The code looks good. It is likely that your new dedicated server is running older version of Bash than your last server. Or maybe /usr/local/bin/bash is pointing towards older version.
Run
$ which bash
if the output is other than /usr/local/bin/bash then change the first shebang line to the newer path, if it still does not work
Try replacing third line:
PORTS=(7777:GAME 11000:AUTH 12000:DB)
with
PORTS=('7777:GAME' '11000:AUTH' '12000:DB')
and rerun the script.
If it still does not work then post the BASH version here by running
$ bash --version
try with facing and trailing spaces
PORTS=( 7777:GAME 11000:AUTH 12000:DB )

OSX bash script works but fails in crontab on SFTP

this topic has been discussed at length, however, I have a variant on the theme that I just cannot crack. Two days into this now and decided to ping the community. THx in advance for reading..
Exec. summary is I have a script in OS X that runs fine and executes without issue or error when done manually. When I put the script in the crontab to run daily it still runs but it doesnt run all of the commands (specifically SFTP).
I have read enough posts to go down the path of environment issues, so as you will see below, I hard referenced the location of the SFTP in the event of a PATH issue...
The only thing that I can think of is the IdentityFile. NOTE: I am putting this in the crontab for my user not root. So I understand that it should pickup on the id_dsa.pub that I have created (and that has already been shared with the server)..
I am not trying to do any funky expect commands to bypass the password, etc. I dont know why when run from the cron that it is skipping the SFTP line.
please see the code below.. and help is greatly appreciated.. thx
#!/bin/bash
export DATE=`date +%y%m%d%H%M%S`
export YYMMDD=`date +%y%m%d`
PDATE=$DATE
YDATE=$YYMMDD
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
FEED="~/Dropbox/"
USER="user"
HOST="host.domain.tld"
A="/tmp/5nPR45bH"
>${A}.file1${PDATE}
>${A}.file2${PDATE}
BYEbye ()
{
rm ${A}.file1${PDATE}
rm ${A}.file2${PDATE}
echo "Finished cleaning internal logs"
exit 0
}
echo "get -r *" >> ${A}.file1${PDATE}
echo "quit" >> ${A}.file1${PDATE}
eval mkdir ${FEED}${YDATE}
eval cd ${FEED}${YDATE}
eval /usr/bin/sftp -b ${A}.file1${PDATE} ${USER}#${HOST}
BYEbye
exit 0
Not an answer, just comments about your code.
The way to handle filenames with spaces is to quote the variable: "$var" -- eval is not the way to go. Get into the habit of quoting all variables unless you specifically want to use the side effects of not quoting.
you don't need to export your variables unless there's a command you call that expects to see them in the environment.
you don't need to call date twice because the YYMMDD value is a substring of the DATE: YYMMDD="${DATE:0:6}"
just a preference: I use $HOME over ~ in a script.
you never use the "file2" temp file -- why do you create it?
since your sftp batch file is pretty simple, you don't really need a file for it:
printf "%s\n" "get -r *" "quit" | sftp -b - "$USER#$HOST"
Here's a rewrite, shortened considerably:
#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
FEED_DIR="$HOME/Dropbox/$(date +%Y%m%d)"
USER="user"
HOST="host.domain.tld"
mkdir "$FEED_DIR" || { echo "could not mkdir $FEED_DIR"; exit 1; }
cd "$FEED_DIR"
{
echo "get -r *"
echo quit
} |
sftp -b - "${USER}#${HOST}"

Sourcing a script file in bash before starting an executable

I'm trying to write a bash script that "wraps" whatever the user wants to invoke (and its parameters) sourcing a fixed file just before actually invoking it.
To clarify: I have a "ConfigureMyEnvironment.bash" script that must be sourced before starting certain executables, so I'd like to have a "LaunchInMyEnvironment.bash" script that you can use as in:
LaunchInMyEnvironment <whatever_executable_i_want_to_wrap> arg0 arg1 arg2
I tried the following LaunchInMyEnvironment.bash:
#!/usr/bin/bash
launchee="$#"
if [ -e ConfigureMyEnvironment.bash ];
then source ConfigureMyEnvironment.bash;
fi
exec "$launchee"
where I have to use the "launchee" variable to save the $# var because after executing source, $# becomes empty.
Anyway, this doesn't work and fails as follows:
myhost $ LaunchInMyEnvironment my_executable -h
myhost $ /home/me/LaunchInMyEnvironment.bash: line 7: /home/bin/my_executable -h: No such file or directory
myhost $ /home/me/LaunchInMyEnvironment.bash: line 7: exec: /home/bin/my_executable -h: cannot execute: No such file or directory
That is, it seems like the "-h" parameter is being seen as part of the executable filename and not as a parameter... But it doesn't really make sense to me.
I tried also to use $* instead of $#, but with no better outcoume.
What I'm doing wrong?
Andrea.
Have you tried to remove double quotes in exec command?
Try this:
#!/usr/bin/bash
typeset -a launchee
launchee=("$#")
if [ -e ConfigureMyEnvironment.bash ];
then source ConfigureMyEnvironment.bash;
fi
exec "${launchee[#]}"
That will use arrays for storing arguments, so it will handle even calls like "space delimited string" and "string with ; inside"
Upd: simple example
test_array() { abc=("$#"); for x in "${abc[#]}"; do echo ">>$x<<"; done; }
test_array "abc def" ghi
should give
>>abc def<<
>>ghi<<
You might want to try this (untested):
#!/usr/bin/bash
launchee="$1"
shift
if [ -e ConfigureMyEnvironment.bash ];
then source ConfigureMyEnvironment.bash;
fi
exec "$launchee" $#
The syntax for exec is exec command [arguments], however becuase you've quoted $launchee, this is treated as a single argument - i.e., the command, rather than a command and it's arguments. Another variation may be to simply do: exec $#
Just execute it normally without exec
#!/usr/bin/bash
launchee="$#"
if [ -e ConfigureMyEnvironment.bash ];
then source ConfigureMyEnvironment.bash;
fi
$launchee
Try dividing your list of argumets:
ALL_ARG="${#}"
Executable="${1}"
Rest_of_Args=${ALL_ARG##$Executable}
And try then:
$Executable $Rest_of_Args
(or exec $Executable $Rest_of_Args)
Debugger

How can I check if a program exists from a Bash script?

How would I validate that a program exists, in a way that will either return an error and exit, or continue with the script?
It seems like it should be easy, but it's been stumping me.
Answer
POSIX compatible:
command -v <the_command>
Example use:
if ! command -v <the_command> &> /dev/null
then
echo "<the_command> could not be found"
exit
fi
For Bash specific environments:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
Explanation
Avoid which. Not only is it an external process you're launching for doing very little (meaning builtins like hash, type or command are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.
Why care?
Many operating systems have a which that doesn't even set an exit status, meaning the if which foo won't even work there and will always report that foo exists, even if it doesn't (note that some POSIX shells appear to do this for hash too).
Many operating systems make which do custom and evil stuff like change the output or even hook into the package manager.
So, don't use which. Instead use one of these:
command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
(Minor side-note: some will suggest 2>&- is the same 2>/dev/null but shorter – this is untrue. 2>&- closes FD 2 which causes an error in the program when it tries to write to stderr, which is very different from successfully writing to it and discarding the output (and dangerous!))
If your hash bang is /bin/sh then you should care about what POSIX says. type and hash's exit codes aren't terribly well defined by POSIX, and hash is seen to exit successfully when the command doesn't exist (haven't seen this with type yet). command's exit status is well defined by POSIX, so that one is probably the safest to use.
If your script uses bash though, POSIX rules don't really matter anymore and both type and hash become perfectly safe to use. type now has a -P to search just the PATH and hash has the side-effect that the command's location will be hashed (for faster lookup next time you use it), which is usually a good thing since you probably check for its existence in order to actually use it.
As a simple example, here's a function that runs gdate if it exists, otherwise date:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "$#"
else
date "$#"
fi
}
Alternative with a complete feature set
You can use scripts-common to reach your need.
To check if something is installed, you can do:
checkBin <the_command> || errorMessage "This tool requires <the_command>. Install it please, and then run this tool again."
The following is a portable way to check whether a command exists in $PATH and is executable:
[ -x "$(command -v foo)" ]
Example:
if ! [ -x "$(command -v git)" ]; then
echo 'Error: git is not installed.' >&2
exit 1
fi
The executable check is needed because bash returns a non-executable file if no executable file with that name is found in $PATH.
Also note that if a non-executable file with the same name as the executable exists earlier in $PATH, dash returns the former, even though the latter would be executed. This is a bug and is in violation of the POSIX standard. [Bug report] [Standard]
Edit: This seems to be fixed as of dash 0.5.11 (Debian 11).
In addition, this will fail if the command you are looking for has been defined as an alias.
I agree with lhunath to discourage use of which, and his solution is perfectly valid for Bash users. However, to be more portable, command -v shall be used instead:
$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed. Aborting." >&2; exit 1; }
Command command is POSIX compliant. See here for its specification: command - execute a simple command
Note: type is POSIX compliant, but type -P is not.
It depends on whether you want to know whether it exists in one of the directories in the $PATH variable or whether you know the absolute location of it. If you want to know if it is in the $PATH variable, use
if which programname >/dev/null; then
echo exists
else
echo does not exist
fi
otherwise use
if [ -x /path/to/programname ]; then
echo exists
else
echo does not exist
fi
The redirection to /dev/null/ in the first example suppresses the output of the which program.
I have a function defined in my .bashrc that makes this easier.
command_exists () {
type "$1" &> /dev/null ;
}
Here's an example of how it's used (from my .bash_profile.)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
Expanding on #lhunath's and #GregV's answers, here's the code for the people who want to easily put that check inside an if statement:
exists()
{
command -v "$1" >/dev/null 2>&1
}
Here's how to use it:
if exists bash; then
echo 'Bash exists!'
else
echo 'Your system does not have Bash'
fi
Try using:
test -x filename
or
[ -x filename ]
From the Bash manpage under Conditional Expressions:
-x file
True if file exists and is executable.
To use hash, as #lhunath suggests, in a Bash script:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
This script runs hash and then checks if the exit code of the most recent command, the value stored in $?, is equal to 1. If hash doesn't find foo, the exit code will be 1. If foo is present, the exit code will be 0.
&> /dev/null redirects standard error and standard output from hash so that it doesn't appear onscreen and echo >&2 writes the message to standard error.
Command -v works fine if the POSIX_BUILTINS option is set for the <command> to test for, but it can fail if not. (It has worked for me for years, but I recently ran into one where it didn't work.)
I find the following to be more failproof:
test -x "$(which <command>)"
Since it tests for three things: path, existence and execution permission.
There are a ton of options here, but I was surprised no quick one-liners. This is what I used at the beginning of my scripts:
[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }
[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }
This is based on the selected answer here and another source.
If you check for program existence, you are probably going to run it later anyway. Why not try to run it in the first place?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
It's a more trustworthy check that the program runs than merely looking at PATH directories and file permissions.
Plus you can get some useful result from your program, such as its version.
Of course the drawbacks are that some programs can be heavy to start and some don't have a --version option to immediately (and successfully) exit.
Check for multiple dependencies and inform status to end users
for cmd in latex pandoc; do
printf '%-10s' "$cmd"
if hash "$cmd" 2>/dev/null; then
echo OK
else
echo missing
fi
done
Sample output:
latex OK
pandoc missing
Adjust the 10 to the maximum command length. It is not automatic, because I don't see a non-verbose POSIX way to do it:
How can I align the columns of a space separated table in Bash?
Check if some apt packages are installed with dpkg -s and install them otherwise.
See: Check if an apt-get package is installed and then install it if it's not on Linux
It was previously mentioned at: How can I check if a program exists from a Bash script?
I never did get the previous answers to work on the box I have access to. For one, type has been installed (doing what more does). So the builtin directive is needed. This command works for me:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
I wanted the same question answered but to run within a Makefile.
install:
#if [[ ! -x "$(shell command -v ghead)" ]]; then \
echo 'ghead does not exist. Please install it.'; \
exit -1; \
fi
It could be simpler, just:
#!/usr/bin/env bash
set -x
# if local program 'foo' returns 1 (doesn't exist) then...
if ! type -P foo; then
echo 'crap, no foo'
else
echo 'sweet, we have foo!'
fi
Change foo to vi to get the other condition to fire.
hash foo 2>/dev/null: works with Z shell (Zsh), Bash, Dash and ash.
type -p foo: it appears to work with Z shell, Bash and ash (BusyBox), but not Dash (it interprets -p as an argument).
command -v foo: works with Z shell, Bash, Dash, but not ash (BusyBox) (-ash: command: not found).
Also note that builtin is not available with ash and Dash.
zsh only, but very useful for zsh scripting (e.g. when writing completion scripts):
The zsh/parameter module gives access to, among other things, the internal commands hash table. From man zshmodules:
THE ZSH/PARAMETER MODULE
The zsh/parameter module gives access to some of the internal hash ta‐
bles used by the shell by defining some special parameters.
[...]
commands
This array gives access to the command hash table. The keys are
the names of external commands, the values are the pathnames of
the files that would be executed when the command would be in‐
voked. Setting a key in this array defines a new entry in this
table in the same way as with the hash builtin. Unsetting a key
as in `unset "commands[foo]"' removes the entry for the given
key from the command hash table.
Although it is a loadable module, it seems to be loaded by default, as long as zsh is not used with --emulate.
example:
martin#martin ~ % echo $commands[zsh]
/usr/bin/zsh
To quickly check whether a certain command is available, just check if the key exists in the hash:
if (( ${+commands[zsh]} ))
then
echo "zsh is available"
fi
Note though that the hash will contain any files in $PATH folders, regardless of whether they are executable or not. To be absolutely sure, you have to spend a stat call on that:
if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]
then
echo "zsh is available"
fi
The which command might be useful. man which
It returns 0 if the executable is found and returns 1 if it's not found or not executable:
NAME
which - locate a command
SYNOPSIS
which [-a] filename ...
DESCRIPTION
which returns the pathnames of the files which would
be executed in the current environment, had its
arguments been given as commands in a strictly
POSIX-conformant shell. It does this by searching
the PATH for executable files matching the names
of the arguments.
OPTIONS
-a print all matching pathnames of each argument
EXIT STATUS
0 if all specified commands are
found and executable
1 if one or more specified commands is nonexistent
or not executable
2 if an invalid option is specified
The nice thing about which is that it figures out if the executable is available in the environment that which is run in - it saves a few problems...
Use Bash builtins if you can:
which programname
...
type -P programname
For those interested, none of the methodologies in previous answers work if you wish to detect an installed library. I imagine you are left either with physically checking the path (potentially for header files and such), or something like this (if you are on a Debian-based distribution):
dpkg --status libdb-dev | grep -q not-installed
if [ $? -eq 0 ]; then
apt-get install libdb-dev
fi
As you can see from the above, a "0" answer from the query means the package is not installed. This is a function of "grep" - a "0" means a match was found, a "1" means no match was found.
This will tell according to the location if the program exist or not:
if [ -x /usr/bin/yum ]; then
echo "This is Centos"
fi
I'd say there isn't any portable and 100% reliable way due to dangling aliases. For example:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
Of course, only the last one is problematic (no offence to Ringo!). But all of them are valid aliases from the point of view of command -v.
In order to reject dangling ones like ringo, we have to parse the output of the shell built-in alias command and recurse into them (command -v isn't a superior to alias here.) There isn't any portable solution for it, and even a Bash-specific solution is rather tedious.
Note that a solution like this will unconditionally reject alias ls='ls -F':
test() { command -v $1 | grep -qv alias }
If you guys/gals can't get the things in answers here to work and are pulling hair out of your back, try to run the same command using bash -c. Just look at this somnambular delirium. This is what really happening when you run $(sub-command):
First. It can give you completely different output.
$ command -v ls
alias ls='ls --color=auto'
$ bash -c "command -v ls"
/bin/ls
Second. It can give you no output at all.
$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found
#!/bin/bash
a=${apt-cache show program}
if [[ $a == 0 ]]
then
echo "the program doesn't exist"
else
echo "the program exists"
fi
#program is not literal, you can change it to the program's name you want to check
The hash-variant has one pitfall: On the command line you can for example type in
one_folder/process
to have process executed. For this the parent folder of one_folder must be in $PATH. But when you try to hash this command, it will always succeed:
hash one_folder/process; echo $? # will always output '0'
I second the use of "command -v". E.g. like this:
md=$(command -v mkdirhier) ; alias md=${md:=mkdir} # bash
emacs="$(command -v emacs) -nw" || emacs=nano
alias e=$emacs
[[ -z $(command -v jed) ]] && alias jed=$emacs
I had to check if Git was installed as part of deploying our CI server. My final Bash script was as follows (Ubuntu server):
if ! builtin type -p git &>/dev/null; then
sudo apt-get -y install git-core
fi
To mimic Bash's type -P cmd, we can use the POSIX compliant env -i type cmd 1>/dev/null 2>&1.
man env
# "The option '-i' causes env to completely ignore the environment it inherits."
# In other words, there are no aliases or functions to be looked up by the type command.
ls() { echo 'Hello, world!'; }
ls
type ls
env -i type ls
cmd=ls
cmd=lsx
env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }
If there isn't any external type command available (as taken for granted here), we can use POSIX compliant env -i sh -c 'type cmd 1>/dev/null 2>&1':
# Portable version of Bash's type -P cmd (without output on stdout)
typep() {
command -p env -i PATH="$PATH" sh -c '
export LC_ALL=C LANG=C
cmd="$1"
cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`"
[ $? != 0 ] && exit 1
case "$cmd" in
*\ /*) exit 0;;
*) printf "%s\n" "error: $cmd" 1>&2; exit 1;;
esac
' _ "$1" || exit 1
}
# Get your standard $PATH value
#PATH="$(command -p getconf PATH)"
typep ls
typep builtin
typep ls-temp
At least on Mac OS X v10.6.8 (Snow Leopard) using Bash 4.2.24(2) command -v ls does not match a moved /bin/ls-temp.
My setup for a Debian server:
I had the problem when multiple packages contained the same name.
For example apache2. So this was my solution:
function _apt_install() {
apt-get install -y $1 > /dev/null
}
function _apt_install_norecommends() {
apt-get install -y --no-install-recommends $1 > /dev/null
}
function _apt_available() {
if [ `apt-cache search $1 | grep -o "$1" | uniq | wc -l` = "1" ]; then
echo "Package is available : $1"
PACKAGE_INSTALL="1"
else
echo "Package $1 is NOT available for install"
echo "We can not continue without this package..."
echo "Exitting now.."
exit 0
fi
}
function _package_install {
_apt_available $1
if [ "${PACKAGE_INSTALL}" = "1" ]; then
if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
echo "package is already_installed: $1"
else
echo "installing package : $1, please wait.."
_apt_install $1
sleep 0.5
fi
fi
}
function _package_install_no_recommends {
_apt_available $1
if [ "${PACKAGE_INSTALL}" = "1" ]; then
if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
echo "package is already_installed: $1"
else
echo "installing package : $1, please wait.."
_apt_install_norecommends $1
sleep 0.5
fi
fi
}

Resources