I'm trying to write a script that uses 'get-iplayer' and will be used on differing distros. On debian it is in '/usr/bin/get-iplayer', but on centos, for example, it is in '/usr/bin/get_iplayer'.
I've been able to check if its even installed with -
if [[ -f "/usr/bin/get-iplayer" ]] || [[ -f "/usr/bin/get_iplayer" ]]
then
echo ;
else
echo "$(tput setaf 1) $(tput setab 7) Error: 'get-iplayer' or 'get_iplayer' is not installed. Please install it. $(tput sgr 0)"
fi
How then can I call it when it could be known by two different names please?
if [[ -x "/usr/bin/get-iplayer" ]]
then player="/usr/bin/get-iplayer"
elif [[ -x "/usr/bin/get_iplayer" ]]
then player="/usr/bin/get_iplayer"
else echo "$0: error: neither get-iplayer nor get_iplayer is installed in /usr/bin" >&2
exit 1
fi
# Run it
"$player" ...
Test both paths/names, then set an alias within your script that points to the one that was found. Use that alias for the remainder of the script.
Very similar to Jonathan Leffler's answer, but using a shell function instead of parameter expansion:
get_iplayer () {
if [[ -x "/usr/bin/get-iplayer" ]]
then /usr/bin/get-iplayer "$#"
elif [[ -x "/usr/bin/get_iplayer" ]]
then /usr/bin/get_iplayer "$#"
else echo "$0: error: neither get-iplayer nor get_iplayer is installed in /usr/bin" >&2
exit 1
fi
}
Debian actually has both get-iplayer and get_iplayer. get_iplayer is the real name of the script. Debian adds the symlink get-iplayer because the hyphenated name is in line with their package naming convention (and thus the name of the package). You should be able to use get_iplayer on any system, as this is the upstream canonical name and it would be bad practice to alter it.
Related
I'm trying to write a script that will only accept exactly one argument. I'm still learning so I don't understand what's wrong with my code. I don't understand why, even though I change the number of inputs the code just exits. (Note: I'm going to use $dir for later if then statements but I haven't included it.)
#!/bin/bash
echo -n "Specify the name of the directory"
read dir
if [ $# -ne 1 ]; then
echo "Script requires one and only one argument"
exit
fi
You can use https://www.shellcheck.net/ to double check your syntax.
$# tells you how many arguments the script was called with.
Here you have two options.
Option 1: Use arguments
#!/bin/bash
if [[ $# -ne 1 ]]
then
echo "Script requires one and only one argument"
exit 1
else
echo "ok, arg1 is $1"
fi
To call the script do: ./script.bash argument
Use [[ ]] for testing conditions (http://mywiki.wooledge.org/BashFAQ/031)
exit 1: by default when a script exists with a 0 status code, it means it worked ok. Here since it is an error, specify a non-zero value.
Option 2: Do not use arguments, ask the user for a value.
Note: this version does not use arguments at all.
#!/bin/bash
read -r -p "Specify the name of the directory: " dir
if [[ ! -d "$dir" ]]
then
echo "Error, directory $dir does not exist."
exit 1
else
echo "ok, directory $dir exists."
fi
To call the script do: ./script.bash without any arguments.
You should research bash tutorials to learn how to use arguments.
clang++: command not found
OS: Ubuntu 20.4 LTS
clang --version: 10.0.0
clang++ work outside of this program. But when I run this program show this error message clang++ command not found
PATH=/home/musleh/programming/cpp
DIR=''
FILE=''
execute () {
cd ${PATH}/${DIR}
clang++ ${FILE} -o a
time ./a
rm a
if [[ $? -ne 0 ]]
then
echo "***************************Program Fail***************************"
fi
}
while getopts i:d: OPTION
do
case ${OPTION} in
d)
DIR=${OPTARG}
;;
i)
FILE=${OPTARG}
;;
?)
usage
;;
esac
done
if [[ $# -lt 4 ]]
then
usage
elif [[ ! -d ${PATH}/${DIR} ]]
then
echo "${DIR} dir not found!" >&2
elif [[ ! -f ${PATH}/${DIR}/${FILE} ]]
then
echo "${FILE} file not found!" >&2
else
execute
fi```
PATH=/home/musleh/programming/cpp
Is very probably wrong and should be instead perhaps
PATH=/usr/bin:/bin:/usr/local/bin:$HOME/programming/cpp
export PATH
Read much more more about the PATH variable and execvp(3) (which most shells use)
Use strace(1) on your shell script. Read Advanced Linux Programming and more about syscalls(2).
Study for inspiration the source code of GNU bash and read its documentation. It is free software so your are allowed to study (and perhaps improve) its source code.
Of course, clang++ needs to be installed. Check by using the which command. Or view your PATH variable using echo $PATH
See also this
Here a script that try to do a 'ifndef' (ifndef.sh):
#!/bin/bash
################# __ifndef__ #################
MAIN_SCRIPT=$(cd "$(dirname "$0")" && pwd -P)"/$(basename $0)"
script_path=$(echo $MAIN_SCRIPT | sed -e "s#/#_#g" -e "s#\.#_#g" -e "s#-#_#")
define_var="ALREADY_SOURCED_$script_path"
echo "Main_script: $MAIN_SCRIPT"
echo "script_path: $script_path"
echo "define_var: $define_var"
echo "??: ${!define_var}"
[[ ! -z ${!define_var} ]] && return
export ALREADY_SOURCED_$script_path="defined"
################# __ifndef__ #################
When I try to execute this code in an another file (main.sh):
. ./ifndef.sh
echo "TEST !"
With sudo, I always get this error:
./main.sh: 10: ./ifndef.sh: Bad substitution
The command I used to launch the script is: ./main.sh
I don't know why ${!define_var} is problematic.
You can't run a bash script with sh -- it can only be run with bash. In particular, you're using several features here (including both indirect expansion a la ${!foo} and the extended test operator [[ ]]) that aren't part of the POSIX sh standard, and so aren't guaranteed to be available when run with sh.
Instead of running sh yourscript, run bash yourscript or just ./yourscript (with a shebang set and execute permissions granted); and instead of starting it with #!/bin/sh, start it with #!/bin/bash. If this is being sourced, ensure that the script you're sourcing it from is itself started with bash.
The other thing here is that your escaping isn't complete enough to ensure that your $script_path really is a valid shell variable name. Instead of trying to fix that, just switch to using an associative array -- that ways keys can contain any character other than NUL, which is invalid in UNIX paths anyhow:
#!/usr/bin/env bash
# uses the first bash interpreter on the PATH, so on MacOS X, can use MacPorts bash
# if sourcing this, be sure to do the same in the script you're sourcing it from.
### start version check
[ -n "$BASH_VERSION" ] || {
echo "Current shell is not bash; must use bash 4.0 or later" >&2
return 1 || exit 1
}
[[ $BASH_VERSION =~ ^([4-9][.]|[0-9][0-9]+) ]] || {
echo "Bash version 4 or newer is required (using $BASH_VERSION)" >&2
return 1 || exit 1
}
### end version check
### start redefinition check
declare -A already_sourced
[[ ${already_sourced[$BASH_SOURCE]} ]] && return
already_sourced[$BASH_SOURCE]=1
### end redefinition check
The above requires Bash 4.x, which isn't shipped with MacOS, but is available through MacPorts.
Using BASH you can skip all sed do all the replacements in BASH itself:
#!/bin/bash
################# __ifndef__ #################
MAIN_SCRIPT=$(cd "$(dirname "$0")" && pwd -P)"/$(basename $0)"
script_path="${MAIN_SCRIPT//[\/.-]/_}"
echo "Main_script: $MAIN_SCRIPT"
echo "script_path: $script_path"
define_var="ALREADY_SOURCED_$script_path"
echo "define_var: $define_var"
echo "??: ${!define_var}"
[[ ! -z ${!define_var} ]] && return
declare -x $define_var="defined"
echo "##: ${!define_var}"
################# __ifndef__ #################
My bad !
I just had to add '#!/bin/bash' in the inner script (main.sh here)
I've a question on how to tell which shell the user is using. Suppose a script that if the user is using zsh, then put PATH to his .zshrc and if using bash should put in .bashrc. And set rvmrc accordingly.
#!/usr/bin/env bash
export PATH='/usr/local/bin:$PATH' >> ~/.zshrc
source ~/.zshrc
I've tried the following but it does not work : (
if [[ $0 == "bash" ]]; then
export PATH='/usr/local/bin:$PATH' >> ~/.bashrc
elif [[ $0 == "zsh" ]]; then
export PATH='/usr/local/bin:$PATH' >> ~/.zshrc
fi
# ... more commands ...
if [[ $0 == "bash" ]]; then
[[ -s '/Users/`whoami`/.rvm/scripts/rvm' ]] && source '/Users/`whoami`/.rvm/scripts/rvm' >> ~/.bashrc
source ~/.bashrc
elif [[ $0 == "zsh" ]]; then
[[ -s '/Users/`whoami`/.rvm/scripts/rvm' ]] && source '/Users/`whoami`/.rvm/scripts/rvm' >> ~/.zshrc
source ~/.zshrc
fi
If the shell is Zsh, the variable $ZSH_VERSION is defined. Likewise for Bash and $BASH_VERSION.
if [ -n "$ZSH_VERSION" ]; then
# assume Zsh
elif [ -n "$BASH_VERSION" ]; then
# assume Bash
else
# assume something else
fi
However, these variables only tell you which shell is being used to run the above code. So you would have to source this fragment in the user's shell.
As an alternative, you could use the $SHELL environment variable (which should contain absolute path to the user's preferred shell) and guess the shell from the value of that variable:
case $SHELL in
*/zsh)
# assume Zsh
;;
*/bash)
# assume Bash
;;
*)
# assume something else
esac
Of course the above will fail when /bin/sh is a symlink to /bin/bash.
If you want to rely on $SHELL, it is safer to actually execute some code:
if [ -n "$($SHELL -c 'echo $ZSH_VERSION')" ]; then
# assume Zsh
elif [ -n "$($SHELL -c 'echo $BASH_VERSION')" ]; then
# assume Bash
else
# assume something else
fi
This last suggestion can be run from a script regardless of which shell is used to run the script.
Just do echo $0
it says -zsh if it's zsh and -bash if it's bash
EDIT: Sometimes it returns -zsh and sometimes zsh and the same with bash, idk why.
A word of warning: the question you seem to have asked, the question you meant to ask, and the question you should have asked are three different things.
“Which shell the user is using” is ambiguous. Your attempt looks like you're trying to determine which shell is executing your script. That's always going to be whatever you put in the #! line of the script, unless you meant your users to edit that script, so this isn't useful to you.
What you meant to ask, I think, is what the user's favorite shell is. This can't be determined fully reliably, but you can cover most cases. Check the SHELL environment variable. If it contains fish, zsh, bash, ksh or tcsh, the user's favorite shell is probably that shell. However, this is the wrong question for your problem.
Files like .bashrc, .zshrc, .cshrc and so on are shell initialization files. They are not the right place to define environment variables. An environment variable defined there would only be available in a terminal where the user launched that shell and not in programs started from a GUI. The definition would also override any customization the user may have done in a subsession.
The right place to define an environment variable is in a session startup file. This is mostly unrelated to the user's choice of shell. Unfortunately, there's no single place to define environment variables. On a lot of systems, ~/.profile will work, but this is not universal. See https://unix.stackexchange.com/questions/4621/correctly-setting-environment and the other posts I link to there for a longer discussion.
You can simply try
echo $SHELL
the other answers fail with set -u
if [ ! -z ${ZSH_VERSION+x} ]; then
echo "this is zsh"
echo ${(%):-%x}
elif [ ! -z ${BASH_VERSION+x} ]; then
echo "this is bash"
echo $BASH_SOURCE
else
echo "not recognized"
fi
An alternative, might not work for all shells.
for x in $(ps -p $$)
do
ans=$x
done
echo $ans
Myself having a similar problem, settled for:
_shell="$(ps -p $$ --no-headers -o comm=)"
if [[ $_shell == "zsh" ]]; then
read -q -s "?Do it?: "
fi
elif [[ $_shell == "bash" || $_shell == "sh" ]]; then
read -n 1 -s -p "Do it [y/n] "
fi
Here is how I am doing it based on a previous answer from Gilles :
if [ -n "$ZSH_VERSION" ]; then
SHELL_PROFILE="$HOME/.zprofile"
else
SHELL_PROFILE="$HOME/.bash_profile"
fi
echo "export VAR1=whatever" >> $SHELL_PROFILE
echo "INFO: Refreshing your shell profile: $SHELL_PROFILE"
if [ -n "$ZSH_VERSION" ]; then
exec zsh --login
else
source $SHELL_PROFILE
fi
This question already has answers here:
How can I check if a program exists from a Bash script?
(39 answers)
Closed 4 years ago.
In a bash script, I need to determine whether an executable named foo is on the PATH.
You could also use the Bash builtin type -P:
help type
cmd=ls
[[ $(type -P "$cmd") ]] && echo "$cmd is in PATH" ||
{ echo "$cmd is NOT in PATH" 1>&2; exit 1; }
You can use which:
path_to_executable=$(which name_of_executable)
if [ -x "$path_to_executable" ] ; then
echo "It's here: $path_to_executable"
fi
TL;DR:
In bash:
function is_bin_in_path {
builtin type -P "$1" &> /dev/null
}
Example usage of is_bin_in_path:
% is_bin_in_path ls && echo "found in path" || echo "not in path"
found in path
In zsh:
Use whence -p instead.
For a version that works in both {ba,z}sh:
# True if $1 is an executable in $PATH
# Works in both {ba,z}sh
function is_bin_in_path {
if [[ -n $ZSH_VERSION ]]; then
builtin whence -p "$1" &> /dev/null
else # bash:
builtin type -P "$1" &> /dev/null
fi
}
To test that ALL given commands are executables in $PATH:
# True iff all arguments are executable in $PATH
function is_bin_in_path {
if [[ -n $ZSH_VERSION ]]; then
builtin whence -p "$1" &> /dev/null
else # bash:
builtin type -P "$1" &> /dev/null
fi
[[ $? -ne 0 ]] && return 1
if [[ $# -gt 1 ]]; then
shift # We've just checked the first one
is_bin_in_path "$#"
fi
}
Example usage:
is_bin_in_path ssh-agent ssh-add && setup_ssh_agent
Non-solutions to avoid
This is not a short answer because the solution must correctly handle:
Functions
Aliases
Builtin commands
Reserved words
Examples which fail with plain type (note the token after type changes):
$ alias foo=ls
$ type foo && echo "in path" || echo "not in path"
foo is aliased to `ls'
in path
$ type type && echo "in path" || echo "not in path"
type is a shell builtin
in path
$ type if && echo "in path" || echo "not in path"
if is a shell keyword
in path
Note that in bash, which is not a shell builtin (it is in zsh):
$ PATH=/bin
$ builtin type which
which is /bin/which
This answer says why to avoid using which:
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.
In this case, also avoid command -v
The answer I just quoted from suggests using command -v, however this doesn't apply to the current "is the executable in $PATH?" scenario: it will fail in exactly the ways I've illustrated with plain type above.
Correct solutions
In bash we need to use type -P:
-P force a PATH search for each NAME, even if it is an alias,
builtin, or function, and returns the name of the disk file
that would be executed
In zsh we need to use whence -p:
-p Do a path search for name even if it is an alias,
reserved word, shell function or builtin.
You can use the command builtin, which is POSIX compatible:
if [ -x "$(command -v "$cmd")" ]; then
echo "$cmd is in \$PATH"
fi
The executable check is needed because command -v detects functions and aliases as well as executables.
In Bash, you can also use type with the -P option, which forces a PATH search:
if type -P "$cmd" &>/dev/null; then
echo "$cmd is in \$PATH"
fi
As already mentioned in the comments, avoid which as it requires launching an external process and might give you incorrect output in some cases.
if command -v foo ; then foo ; else echo "foo unavailable" ; fi
Use which
$ which myprogram
We can define a function for checking whether as executable exists by using which:
function is_executable() {
which "$#" &> /dev/null
}
The function is called just like you would call an executable. "$#" ensures that which gets exactly the same arguments as are given to the function.
&> /dev/null ensures that whatever is written to stdout or stderr by which is redirected to the null device (which is a special device which discards the information written to it) and not written to stdout or stderr by the function.
Since the function doesn't explicitly return with an return code, when it does return, the exit code of the latest executed executable—which in this case is which—will be the return code of the function. which will exit with a code that indicates success if it is able to find the executable specified by the argument to the function, otherwise with an exit code that indicates failure. This behavior will automatically be replicated by is_executable.
We can then use that function to conditionally do something:
if is_executable name_of_executable; then
echo "name_of_executable was found"
else
echo "name_of_executable was NOT found"
fi
Here, if executes the command(s) written between it and then—which in our case is is_executable name_of_executable—and chooses the branch to execute based on the return code of the command(s).
Alternatively, we can skip defining the function and use which directly in the if-statement:
if which name_of_executable &> /dev/null; then
echo "name_of_executable was found"
else
echo "name_of_executable was NOT found"
fi
However, I think this makes the code slightly less readable.