How, in BASH, I could check if a given argument to a script is a "commands name"?
For example: I know that you can check if a path is a folder with [[ -d $path ]] or if it is plain file with [[ -f $path ]].
I want that my ./script will only accept commands name.
With [[ -f "$path" && -x "$path" ]] you can check if it is a file and it has execution permissions, you can see the full list of expressions in man test.
This way you check if the argument can be executed directly from the shell (for example: if <command> is ls this return Yes, if <command> is las this will return No).
if [[ `which <command> &> /dev/null` ]]; then
echo "Yes";
else
echo "No";
fi
With a simpler [[ -f $path && -x $path ]] you can check if $path can be executed... but this does not mean that writing the "name of the command" in the shell, it will be executed (you have to make sure that the $path is in the $PATH environmental variable.
A simple script that uses the idea above and exit in the case the argument is not a command could be something like:
#!/bin/bash
if [[ `which $1 &> /dev/null` ]]; then
echo "Execute the script"
else
echo "Error! Usage: "`basename $0`" <command_name>"
fi
Tell if you need something different...
Hi if i understand you right you can try the following:
SCRIPTNAME=$(basename "$0") # in your case -> script
Now you can check this:
if [ SCRIPTNAME -eq "$SCRIPTNAME" ]
then
# your stuff here
fi
Or do you want something other?
I'm trying to write a generic shell script to archive X days older files matching pattern passed as parameter. I'm having tough time making the boolean parameter, regex parameter and regex variable in script work across mac and ubuntu. I'm new to shell scripting. Any suggestion related to the problem or best practices are welcome. Following is the script:
#!/usr/bin/env bash
#Default source dir
SOURCE_DIR=./logs/
# Delete by default
DELETE=YES
# Archive files older by these many days
OLD=7
# Pattern to archive
PATTERN="*.log*"
# Use -gt 1 to consume two arguments per pass in the loop
# (each argument has a corresponding value to go with it).
while [[ $# -gt 1 ]]; do
key="$1"
case $key in
-s|--source_dir)
SOURCE_DIR="$2"
shift # past argument
;;
-d|--dest_dir)
DEST_DIR="$2"
shift # past argument
;;
-o|--days)
OLD="$2"
shift # past argument
;;
-p|--pattern)
PATTERN="$2"
shift # past argument
;;
-n|--no-delete)
DELETE=NO
;;
*)
# unknown option
;;
esac
shift # past argument or value
done
if [[ ! -d "$SOURCE_DIR" ]]; then
echo 'Archive source does not exist'
exit 1
fi
SOURCE_DIR=${SOURCE_DIR%/}
if [[ -z "$DEST_DIR" ]]; then
DEST_DIR="${SOURCE_DIR%/}/backup"
fi
DEST_DIR=${DEST_DIR%/}
if [[ ! -d "$DEST_DIR" ]]; then
echo 'Creating destination '$DEST_DIR
mkdir -p -- "$DEST_DIR"
fi
echo $SOURCE_DIR
echo $DEST_DIR
echo $OLD
echo $PATTERN
echo $DELETE
files=$(find $SOURCE_DIR -mtime +$OLD -type f -name $PATTERN)
echo $files
if [[ $DELETE = YES ]]; then
echo "Delete files"
else
echo "Don't delete files"
fi
Outpout on Mac:
(mysql30):recon-etl anshuc$ ./archive.sh -s junk/ -p *.py -o 10 -n
junk
junk/backup
10
*.py
YES
junk/__init__.py junk/client.py
Delete files
(mysql30):recon-etl anshuc$
Output on ubuntu 14.04
anshuc:~/workspace/xyz$ ./archive.sh -s ae/tools/ -d ae/logs/backup/ -p *.py -n -o 420
ae/tools
ae/logs/backup
420
*.py
NO
ae/tools/services/__init__.py ae/tools/__init__.py
Don't delete files
anshuc:~/workspace/xyz$
DELETE not working on mac is the concern. Also, I was having problem with PATTERN argument before. Though on trial and error I have come across a way to do. But am not sure of side-effects in case someone doesn't use quotes or any other intricacies that may be involved. A li'l input on that would make me more knowledged. :-)
TIA
My before_install in my .travis.yml reads
before_install:
- . scripts/get_racket.sh
- alias racket="${RACKET_DIR}/bin/racket"
I also have a script get_racket.sh which reads
#!/bin/bash
if [[ -z "$RACKET_VERSION" ]]; then
echo "Racket version environment variable not set, setting default"
export RACKET_VERSION=HEAD # set default Racket version
echo "Version: $RACKET_VERSION"
fi
if [[ -z "$RACKET_DIR" ]]; then
echo "Racket directory environment variable not set, setting default"
export RACKET_DIR='/usr/racket' # set default Racket directory
echo "Directory: $RACKET_DIR"
fi
if [ ! -e cache ] || [ ! -d cache ]; then
echo "Creating cache folder ..."
mkdir cache
fi
cd cache
INSTALL=$(ls | grep '^racket*.sh' | tr -d '[:blank:]')
if [[ ! -e "$RACKET_DIR" ]] || [[ ! -d "$RACKET_DIR" ]]; then
if [[ -z "$INSTALL" ]]; then
echo "Racket installation script not found, building."
if [ ! -e travis-racket ] || [ ! -d travis-racket ] \
|| [ ! -e travis-racket/install-racket.sh ] \
|| [ ! -f travis-racket/install-racket.sh ]; then
git clone https://github.com/greghendershott/travis-racket.git
fi
bash < travis-racket/install-racket.sh
else
"./$INSTALL"
fi
fi
which racket &>/dev/null
ESTATUS=$?
if [[ -n "$ESTATUS" ]]; then
echo "Adding racket to PATH"
export PATH="${PATH}:${RACKET_DIR}/bin"
fi
alias racket='$RACKET_DIR/bin/racket'
cd ..
but in a script that uses racket later in my build chain, I keep getting
racket: command not found
As you can see in the above snippets, I have tried a few workarounds to install (and later cache for faster builds) racket without sudo privileges (because this is a restriction of Travis CI's Container-based infrastructure). Any help would be much appreciated, I'm stumped.
You need to figure out whether this install script you've shown successfully puts a working Racket binary anywhere on the disk. Maybe it didn't even compile, or maybe it tried to install in /usr/bin, where you don't have write access without sudo, or maybe there's something wrong with the binary. Find the binary, make sure it works.
If it does work, you need to pay attention to where your script puts Racket. Does it go to /usr/bin, $HOME, or someplace else entirely?
Finally, you need to figure out where the failing script is looking for Racket. The line where you set the $PATH will not affect the $PATH as seen from another shell script. I'd bet it's installing somewhere that's not in the default $PATH, and your failing script is looking only in the default $PATH.
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.
I am wondering what's the easiest way to check if a program is executable with bash, without executing it ? It should at least check whether the file has execute rights, and is of the same architecture (for example, not a windows executable or another unsupported architecture, not 64 bits if the system is 32 bits, ...) as the current system.
Take a look at the various test operators (this is for the test command itself, but the built-in BASH and TCSH tests are more or less the same).
You'll notice that -x FILE says FILE exists and execute (or search) permission is granted.
BASH, Bourne, Ksh, Zsh Script
if [[ -x "$file" ]]
then
echo "File '$file' is executable"
else
echo "File '$file' is not executable or found"
fi
TCSH or CSH Script:
if ( -x "$file" ) then
echo "File '$file' is executable"
else
echo "File '$file' is not executable or found"
endif
To determine the type of file it is, try the file command. You can parse the output to see exactly what type of file it is. Word 'o Warning: Sometimes file will return more than one line. Here's what happens on my Mac:
$ file /bin/ls
/bin/ls: Mach-O universal binary with 2 architectures
/bin/ls (for architecture x86_64): Mach-O 64-bit executable x86_64
/bin/ls (for architecture i386): Mach-O executable i386
The file command returns different output depending upon the OS. However, the word executable will be in executable programs, and usually the architecture will appear too.
Compare the above to what I get on my Linux box:
$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), stripped
And a Solaris box:
$ file /bin/ls
/bin/ls: ELF 32-bit MSB executable SPARC Version 1, dynamically linked, stripped
In all three, you'll see the word executable and the architecture (x86-64, i386, or SPARC with 32-bit).
Addendum
Thank you very much, that seems the way to go. Before I mark this as my answer, can you please guide me as to what kind of script shell check I would have to perform (ie, what kind of parsing) on 'file' in order to check whether I can execute a program ? If such a test is too difficult to make on a general basis, I would at least like to check whether it's a linux executable or osX (Mach-O)
Off the top of my head, you could do something like this in BASH:
if [ -x "$file" ] && file "$file" | grep -q "Mach-O"
then
echo "This is an executable Mac file"
elif [ -x "$file" ] && file "$file" | grep -q "GNU/Linux"
then
echo "This is an executable Linux File"
elif [ -x "$file" ] && file "$file" | grep q "shell script"
then
echo "This is an executable Shell Script"
elif [ -x "$file" ]
then
echo "This file is merely marked executable, but what type is a mystery"
else
echo "This file isn't even marked as being executable"
fi
Basically, I'm running the test, then if that is successful, I do a grep on the output of the file command. The grep -q means don't print any output, but use the exit code of grep to see if I found the string. If your system doesn't take grep -q, you can try grep "regex" > /dev/null 2>&1.
Again, the output of the file command may vary from system to system, so you'll have to verify that these will work on your system. Also, I'm checking the executable bit. If a file is a binary executable, but the executable bit isn't on, I'll say it's not executable. This may not be what you want.
Seems nobody noticed that -x operator does not differ file with directory.
So to precisely check an executable file, you may use
[[ -f SomeFile && -x SomeFile ]]
Testing files, directories and symlinks
The solutions given here fail on either directories or symlinks (or both). On Linux, you can test files, directories and symlinks with:
if [[ -f "$file" && -x $(realpath "$file") ]]; then .... fi
On OS X, you should be able to install coreutils with homebrew and use grealpath.
Defining an isexec function
You can define a function for convenience:
isexec() {
if [[ -f "$1" && -x $(realpath "$1") ]]; then
true;
else
false;
fi;
}
Or simply
isexec() { [[ -f "$1" && -x $(realpath "$1") ]]; }
Then you can test using:
if `isexec "$file"`; then ... fi
Also seems nobody noticed -x operator on symlinks. A symlink (chain) to a regular file (not classified as executable) fails the test.
First you need to remember that in Unix and Linux, everything is a file, even directories. For a file to have the rights to be executed as a command, it needs to satisfy 3 conditions:
It needs to be a regular file
It needs to have read-permissions
It needs to have execute-permissions
So this can be done simply with:
[ -f "${file}" ] && [ -r "${file}" ] && [ -x "${file}" ]
If your file is a symbolic link to a regular file, the test command will operate on the target and not the link-name. So the above command distinguishes if a file can be used as a command or not. So there is no need to pass the file first to realpath or readlink or any of those variants.
If the file can be executed on the current OS, that is a different question. Some answers above already pointed to some possibilities for that, so there is no need to repeat it here.
To test whether a file itself has ACL_EXECUTE bit set in any of permission sets (user, group, others) regardless of where it resides, i. e. even on a tmpfs with noexec option, use stat -c '%A' to get the permission string and then check if it contains at least a single “x” letter:
if [[ "$(stat -c '%A' 'my_exec_file')" == *'x'* ]] ; then
echo 'Has executable permission for someone'
fi
The right-hand part of comparison may be modified to fit more specific cases, such as *x*x*x* to check whether all kinds of users should be able to execute the file when it is placed on a volume mounted with exec option.
This might be not so obvious, but sometime is required to test the executable to appropriately call it without an external shell process:
function tkl_is_file_os_exec()
{
[[ ! -x "$1" ]] && return 255
local exec_header_bytes
case "$OSTYPE" in
cygwin* | msys* | mingw*)
# CAUTION:
# The bash version 3.2+ might require a file path together with the extension,
# otherwise will throw the error: `bash: ...: No such file or directory`.
# So we make a guess to avoid the error.
#
{
read -r -n 4 exec_header_bytes 2> /dev/null < "$1" ||
{
[[ -x "${1%.exe}.exe" ]] && read -r -n 4 exec_header_bytes 2> /dev/null < "${1%.exe}.exe"
} ||
{
[[ -x "${1%.com}.com" ]] && read -r -n 4 exec_header_bytes 2> /dev/null < "${1%.com}.com"
}
} &&
if [[ "${exec_header_bytes:0:3}" == $'MZ\x90' ]]; then
# $'MZ\x90\00' for bash version 3.2.42+
# $'MZ\x90\03' for bash version 4.0+
[[ "${exec_header_bytes:3:1}" == $'\x00' || "${exec_header_bytes:3:1}" == $'\x03' ]] && return 0
fi
;;
*)
read -r -n 4 exec_header_bytes < "$1"
[[ "$exec_header_bytes" == $'\x7fELF' ]] && return 0
;;
esac
return 1
}
# executes script in the shell process in case of a shell script, otherwise executes as usual
function tkl_exec_inproc()
{
if tkl_is_file_os_exec "$1"; then
"$#"
else
. "$#"
fi
return $?
}
myscript.sh:
#!/bin/bash
echo 123
return 123
In Cygwin:
> tkl_exec_inproc /cygdrive/c/Windows/system32/cmd.exe /c 'echo 123'
123
> tkl_exec_inproc /cygdrive/c/Windows/system32/chcp.com 65001
Active code page: 65001
> tkl_exec_inproc ./myscript.sh
123
> echo $?
123
In Linux:
> tkl_exec_inproc /bin/bash -c 'echo 123'
123
> tkl_exec_inproc ./myscript.sh
123
> echo $?
123