I'm writing a script in bash. It will receive from 2 to 5 arguments. For example:
./foo.sh -n -v SomeString Type Directory
-n, -v and Directory are optional.
If script doesn't receive argument Directory it will search in current directory for a string.
Otherwise it will follow received path and search there. If this directory doesn't exist it will send a message.
The question is: Is there a way to check if the last arg is a path or not?
You can get last argument using variable reference:
numArgs=$#
lastArg="${!numArgs}"
# check if last argument is directory
if [[ -d "$lastArg" ]]; then
echo "it is a directory"
else
echo "it is not a directory"
fi
you can use this:
#!/bin/bash
if [[ -d ${!#} ]]
then
echo "DIR EXISTS"
else
echo "dosen't exists"
fi
First, use getopts to parse the options -n and -v (they will have to be used before any non-options, but that's not usually an issue).
while getopts nv opt; do
case $opt in
n) nflag=1 ;;
v) vflag=1 ;;
*) printf >&2 "Unrecognized option $opt\n"; exit 1 ;;
esac
done
shift $((OPTIND-1))
Now, you will have only your two required arguments, and possibly your third optional argument, in $#.
string_arg=$1
type_arg=$2
dir_arg=$3
if [ -d "$dir_arg" ]; then
# Do something with valid directory
fi
Note that this code will work in any POSIX-compliant shell, not just bash.
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.
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
I use following lines (hope this is best practice if not correct me please) to handle command line options:
#!/usr/bin/bash
read -r -d '' HELP <<EOF
OPTIONS:
-c Enable color output
-d Enable debug output
-v Enable verbose output
-n Only download files (mutually exclusive with -r)
-r Only remove files (mutually exclusive with -n)
-h Display this help
EOF
# DECLARE VARIABLES WITH DEFAULT VALUES
color=0
debug=0
verbose=0
download=0
remove=0
OPTIND=1 # Reset in case getopts has been used previously in the shell
invalid_options=(); # Array for invalid options
while getopts ":cdvnrh" opt; do
echo "Actual opt: $opt"
case $opt in
c)
color=1
;;
d)
debug=1
;;
v)
verbose=1
;;
n)
download=1
;;
r)
remove=1
;;
h)
echo "$HELP"
exit 1
;;
\?)
invalid_options+=($OPTARG)
;;
*)
invalid_options+=($OPTARG)
;;
esac
done
# HANDLE INVALID OPTIONS
if [ ${#invalid_options[#]} -ne 0 ]; then
echo "Invalid option(s):" >&2
for i in "${invalid_options[#]}"; do
echo $i >&2
done
echo "" >&2
echo "$HELP" >&2
exit 1
fi
# SET $1 TO FIRST MASS ARGUMENT, $2 TO SECOND MASS ARGUMENT ETC
shift $((OPTIND - 1))
# HANDLE CORRECT NUMBER OF MASS OPTIONS
if [ $# -ne 2 ]; then
echo "Correct number of mass arguments are 2"
echo "" >&2
echo "$HELP" >&2
exit 1
fi
# HANDLE MUTUALLY EXCLUSIVE OPTIONS
if [ $download -eq 1 ] && [ $remove -eq 1 ]; then
echo "Options for download and remove are mutually exclusive" >&2
echo "$HELP" >&2
exit 1
fi
echo "color: $color"
echo "debug: $debug"
echo "verbose: $verbose"
echo "download: $download"
echo "remove: $remove"
echo "\$1: $1"
echo "\$2: $2"
If I call the script way that mass arguments (those that are not switches or arguments for switches) are last arguments everything is working correctly:
$ ./getopts.sh -c -d -v -r a b
Actual opt: c
Actual opt: d
Actual opt: v
Actual opt: r
color: 1
debug: 1
verbose: 1
download: 0
remove: 1
$1: a
$2: b
The problem is when I want to call the script so the mass arguments are first (or somewhere in the middle of switches that do not use arguments)
$ ./getopts.sh a b -c -d -v -r
Correct number of mass arguments are 2
OPTIONS:
-c Enable color output
-d Enable debug output
-v Enable verbose output
-n Only download files (mutually exclusive with -r)
-r Only remove files (mutually exclusive with -d)
-h Display this help
or
$ ./getopts.sh -c a b -d -v -r
Actual opt: c
Correct number of mass arguments are 2
OPTIONS:
-c Enable color output
-d Enable debug output
-v Enable verbose output
-n Only download files (mutually exclusive with -r)
-r Only remove files (mutually exclusive with -d)
-h Display this help
I think this should be OK according (POSIX) standards, because following syntax which is basically the same is working as expected on my system:
$ cp test1/ test2/ -r
$ cp test1/ -r test2/
I have search over the Internet but only thing that was close to my problem was this one related to C.
getopts automatically breaks the while loop as soon as it detects a non-dash parameter (not including the argument given to dash parameters that take arguments). The POSIX standard is to have dashed parameters come first, and then have files. There's also none of this -- and + crap either. It's plain and simple.
However, Linux isn't Unix or POSIX compliant. It's just in the nature of the GNU utilities to be "better" than the standard Unix utilities. More features, more options, and handling things a bit differently.
On Linux, command line parameters can come after files in many GNU utilities.
For example:
$ cp -R foo bar
Work on my Unix certified Mac OS X and on Linux, However,
$ cp foo bar -R
Only works on Linux.
If you want getopts to work like a lot of Linux utilities, you need to do a wee bit of work.
First, you have to process your arguments yourself, and not depend upon $OPTIND to parse them. You also need to verify that you have an argument.
I came up with this as an example of doing what you want.
#! /bin/bash
while [[ $* ]]
do
OPTIND=1
echo $1
if [[ $1 =~ ^- ]]
then
getopts :a:b:cd parameter
case $parameter in
a) echo "a"
echo "the value is $OPTARG"
shift
;;
b) echo "b"
echo "the value is $OPTARG"
shift
;;
c) echo "c"
;;
d) echo "d"
;;
*) echo "This is an invalid argument: $parameter"
;;
esac
else
other_arguments="$other_arguments $1"
fi
shift
done
echo "$other_arguments"
I now loop as long as $* is set. (Maybe I should use $#?) I have to do a shift at the end of the loop. I also reset $OPTIND to 1 each time because I'm shifting the arguments off myself. $OPTARG is still set, but I have to do another shift to make sure everything works.
I also have to verify if a argument begins with a dash or not using a regular expression in my if statement.
Basic testing shows it works, but I can't say it's error free, but it does give you an idea how you have to handle your program.
There's still plenty of power you're getting from getopts, but it does take a bit more work.
Bash provides two methods for argument parsing.
The built-in command getopts is a newer, easy to use mechanism how to parse arguments but it is not very flexible. getopts does not allow to mix options and mass arguments.
The external command getopt is an older and more complex mechanism to parse arguments. It allows long/short options and the gnu extension allow to mix options and mass arguments.
I wrote a script with mbratch's help.
I run as below;
./scriptname folder1
However, I see neither error nor results and I'm not sure what's wrong.
sh -x ./scriptname folder1
+ MAIN
+ check_directories
+ [ -d ]
This works fine for me:
Note: updated to support additional options.
opt="$1"
folder1="$2"
folder2="$3"
case "$opt" in
-d)
if [ -d "${folder1}" ] && [ -d "${folder2}" ] ; then
for i in "${folder1}/*" ; do
echo "test <$i>"
if [ -f "${folder2}/${i##*/}" ] ; then
echo "<${i##*/}>"
else
echo "! <${i##*/}>"
fi
done
fi
;;
# Example option
-h)
# Print help and exit.
;;
# Default case
*)
echo "Unknown option '$opt'" >&2
exit 1
;;
esac
Try replacing ~/ with $HOME/, and be sure to set folder1 and folder2 before using them. Note also that this will break if your directory or file names include spaces. In that case, use find; check the find man page for details.
Is that the entirety of your script? The variables $folder1 are never defined anywhere. By default, the program will take the first two chunks of text in as $1 and $2, so use those variables instead.
Put some echo statements in there to see what variables have what values.
You have a for loop already, so go with that. However you might have to put the part where you are getting a file list inside of $() to have it assigned to a variable, and then loop over it.
Do a quick search on "Looping through files in bash" and you will find a good template for the for loop.
Can someone show me an example how to use getopts properly or any other technique that I would be able to pass in an argument? I am trying to write this in unix shell/bash. I am seeing there is getopt and getopts and not sure which is better to use. Eventually, I will build this out to add for more options.
In this case, I want to pass the filepath as input to the shell script and place a description in the case it wasn't entered correctly.
export TARGET_DIR="$filepath"
For example: (calling on the command line)
./mytest.sh -d /home/dev/inputfiles
Error msg or prompt for correct usage if running it this way:
./mytest.sh -d /home/dev/inputfiles/
As a user, I would be very annoyed with a program that gave me an error for providing a directory name with a trailing slash. You can just remove it if necessary.
A shell example with pretty complete error checking:
#!/bin/sh
usage () {
echo "usage: $0 -d dir_name"
echo any other helpful text
}
dirname=""
while getopts ":hd:" option; do
case "$option" in
d) dirname="$OPTARG" ;;
h) # it's always useful to provide some help
usage
exit 0
;;
:) echo "Error: -$OPTARG requires an argument"
usage
exit 1
;;
?) echo "Error: unknown option -$OPTARG"
usage
exit 1
;;
esac
done
if [ -z "$dirname" ]; then
echo "Error: you must specify a directory name using -d"
usage
exit 1
fi
if [ ! -d "$dirname" ]; then
echo "Error: the dir_name argument must be a directory
exit 1
fi
# strip any trailing slash from the dir_name value
dirname="${dirname%/}"
For getopts documentation, look in the bash manual
Correction to the ':)' line:
:) echo "Error: -$OPTARG requires an argument"
because if no value got provided after the flag, then OPTARG gets the name of the flag and flag gets set to ":" which in the above sample printed:
Error: -: requires an argument
which wasn't useful info.
Same applies to:
\?) echo "Error: unknown option -$OPTARG"
Thanks for this sample!