Here's the code:
#!/bin/bash
function print_help() {
echo "Just a pile of echos; no other logic here"
}
die() {
printf '%s\n' "$1" >&2
exit 1
}
IpAddress=
SourceDir=
DestDir=
backup=0
restore=0
while :; do
case $1 in
''|-h|\?|--help) print_help
exit 0
;;
-b|--backup)
if [ $restore -eq 0 ]; then
backup=1
echo "Taking backup..."
else
die "ERROR: Can't perform backup and restore at the same time."
fi
;;
-r|--restore)
if [ $backup -eq 0 ]; then
restore=1
echo "Restoring backup..."
else
die "ERROR: Can't perform backup and restore at the same time."
fi
;;
--ip)
if [ "$2" ]; then
ip=$2
shift
else
die 'ERROR: "--ip" requires a non-empty option argument.'
fi
;;
--ip=?*)
IpAddress=${1#*=}
echo "IP Address: $IpAddress"
;;
--source-dir=?*)
SourceDir=${1#*=}
if [ -d "$SourceDir" ]; then
echo "Source Directory: $SourceDir indeed exists"
else
die "ERROR: $SourceDir doesn't exist."
fi
;;
--source-dir=)
die 'ERROR: "--source-dir" requires a non-empty argument.'
;;
--dest-dir=?*)
DestDir=${1#*=}
if [ -w "$DestDir" ]; then
echo "Destination Directory: $DestDir indeed exists and is writable."
else
die "ERROR: $DestDir doesn't exist or is not writable for current user."
fi
;;
--dest-dir=)
die 'ERROR: "--dest-dir" requires a non-empty argument.'
;;
--)
shift
break
;;
-?*)
printf 'Warning: Unknown option (ignoring): %s\n' "$1" >&2
;;
*)
break
esac
shift
done
So far, I've tested two use cases, with directories:
/tmp/back/ (in which user has no read permissions) and
/tmp/back2/ (which fully belongs to the user).
The 1st option launched correctly prints the error message:
$ ./backup.sh -b --source-dir=/opt/ --dest-dir=/tmp/back/
Taking backup...
Source Directory: /opt/ indeed exists
ERROR: /tmp/back/ doesn't exist or is not writable for current user.
Meanwhile the 2nd option confirms that data is OK, and seems to come back to the print_help case:
$ ./backup.sh -b --source-dir=/opt/ --dest-dir=/tmp/back2/
Taking backup...
Source Directory: /opt/ indeed exists
Destination Directory: /tmp/back2/ indeed exists and is writable.
backup.sh: backup.sh <-b|r> [-h] [server IP] <source dir> <dest dir>
Take or restore a backup of files.
What should be corrected here in order to exit the script after all options are checked one time?
The problem is that your while loop is an infinite loop, and you only break out of that loop in two of your 12 cases, or when an error occurs. So unless the code encounters those two cases, or an error, it will keep iterating even when there are no more parameters to read. When all of the parameters have been shifted, $1 will evaluate to an empty string, which is explicitly listed in your first case (''|-h|\?|--help) print_help), resulting in the help message.
The solution is to test for the existence of parameters in your while loop:
if [ $# -eq 0 ]; then
print_help
exit 1
fi
while [ $1 -gt 0 ]; do
case $1 in
-h|\?|--help) print_help
...
done
That way, when the last parameter has been shifted, the loop will end.
When all args ended $1 is '' that is why it comes back to the print_help
What should be corrected here in order to exit the script after all
options are checked one time?
Change print_help pattern to *) and '') should be exit 0
But better change while condition to:
while [[ $1 ]]; do
...
done
And this check could be added at the beginning of the script:
[[ $1 ]] || print_help
to print help message and exit if no args passed to the script.
Remove '' from the case statement and test if there are no arguments before the loop:
[[ $1 ]] || { print_help ; exit 0 ; }
while :; do
case $1 in
-h|\?|--help) print_help
exit 0
;;
Related
I am working on a shell script that will allow for the user to input a file name(s) into the command prompt, and the script will locate the executable file and determine if they could have been executed. However, when I attempt to run it, I get a "line 45 `$#': not a valid identifier" error message.
This is my first script and can't figure out how to address this error. I am hopeful that the rest of the script is ok, but I haven't been able to get past this one error to test it out.
Any help is greatly appreciated!
#!/bin/bash
FINDALL=FALSE
while :
do
case $1 in
-a) FINDALL=TRUE
shift ;;
-h) echo “Usage $0 [-a] commands…”
exit 0 ;;
*) break ;;
esac
done
for $# in $file
do
case $file in
*/*) if [ -x “$file” ]
then
echo $file
elif [ -d “$file” ]
then
echo $file is NOT found
fi
shift ;;
*) FOUND=FALSE ;;
esac
done
case $PATH in
:*) PATH=”.:$PATH” ;;
*::*) PATH=`echo $PATH | sed -e ‘s/::/:.:/g’` ;;
*:) PATH=”$PATH:.” ;;
esac
command=”$1”
IFS=$OLDIFS
IFS=:
set -- $PATH
IFS=$OLDIFS
case $command in
*/*) echo $command ;;
*) FOUND=FALSE
for P
do
if [ ! -d “$P/$command” -a -x “$P/$command” ]
then
FOUND=TRUE
echo $command
break
fi
done
if [ “$FOUND” = FALSE ]
then
echo “Command $command not found in your search path”
fi ;;
esac
This question already has an answer here:
Storing bash script argument with multiple values
(1 answer)
Closed 5 years ago.
I have been researching on using bash scripts to process command-line arguments. I have multiple optional arguments, each of which have one or more operands. An example is:
./script.sh -f file1 file2 -s server1 server2
-f itself is optional, but must be followed by filename; -s is optional, can be used without any operands or operands.
I know I can force putting "" on operands so I only deal with arguments with one operand and can use case $1 $2 shift to process it.
But I am interested in doing so without quotes, just to save some typing for the users.
A rough idea would be read in "$#" as one string, and separate them by space, then locate arguments with -/-- and assign operands following them. Maybe I can use an array to do that?
Any suggestions would be welcome.
Thanks folks for your wonderful suggestions. After spending some more time I resolved to the solution below:
Simply put, I use case and few checks to determine if the argument is an option or not. I use only alter flag variables during argument processing and then use the flags to determine what functions I will perform. In a way that I can have options in different order.
main(){
# flags, 1 is false, 0 is true. it's the damn bash LOCAL_DEPLOY=1
SERVER_DEPLOY=1 DRY_RUN=0
FILES=("${ALLOWEDFILES[#]}");
DEPLOYTARGET=("${ALLOWEDSERVERS[#]}");
if [ $# -eq 0 ]
then
printf -- "Missing optins, perform DRY RUN\nFor help, run with -h/--help\n"
for target in "${FILES[#]}"; do generate "$target"; done
echo "....dry run: markdown files generated in rendered/"
exit 0
fi
while true ; do
case "$1" in
-f |--file) # required operands
case "$2" in
"") die $1 ;;
*)
FILES=($2)
for i in "${FILES[#]}"; do
if is_option $i; then die $1; fi # check for option
if ! check_allowed $i ${ALLOWEDFILES[#]}; then exit 1; fi
done;
shift 2;; # input FILES are good
esac ;;
-l|--local) # no operands expected
DRY_RUN=1 # turn off dryrun
LOCAL_DEPLOY=0 # turn on local deploy
shift ;;
-s|--server) # optional operands
case "$2" in
"") shift ;;
*)
DEPLOYTARGET=($2) # use input
for i in "${DEPLOYTARGET[#]}"; do
if is_option $i; then die $1; fi # check for option
if ! check_allowed $i ${ALLOWEDSERVERS[#]}; then exit 1; fi
done ; shift 2;; # use input value
esac
DRY_RUN=1
SERVER_DEPLOY=0
;;
-n|--dryrun) # dry-run:generate markdown files only
DRY_RUN=0
shift ;;
-h|--help) # docs
print_help
exit 0
;;
--) shift; break ;;
-?*)
printf 'ERROR: Unkown option: %s\nExisting\n\n' "$1" >&2
print_help
exit 1
shift
;;
*)
break ;;
esac
done
echo "choose files: ${FILES[#]}"
echo ""
# dry-run
if [ $DRY_RUN == 0 ]; then
echo "..perform dry run.."
for target in "${FILES[#]}"; do generate "$target"; done
echo "....dry run: markdown files generated in rendered/"
exit 0
fi
# local-deploy
if [ $LOCAL_DEPLOY == 0 ] && [ $SERVER_DEPLOY != 0 ]; then
echo "..deploy locally"
for target in "${FILES[#]}"; do
generate "$target" > /dev/null
deploylocal "$target"
done;
# sync hexo-gcs hexo-yby
cd "$(dirname $HEXOLOCATION)"
./syncRepo.sh
printf -- "....hexo-gcs hexo-yby synced\n"
cd $CURRENTLOCATION
fi
# server-deploy
if [ $SERVER_DEPLOY == 0 ]; then
echo "..deploy on servers: ${DEPLOYTARGET[#]}"
echo ""
for target in "${FILES[#]}"; do # deploy locally
generate "$target" > /dev/null
deploylocal "$target"
done
# sync hexo-gcs hexo-yby
cd "$(dirname $HEXOLOCATION)"
./syncRepo.sh
printf -- "....hexo-gcs hexo-yby synced\n"
cd $CURRENTLOCATION
# deploy to selected server: git or gcp
for dt in "${DEPLOYTARGET[#]}"; do
deployserver $dt
done
fi
}
Long story short, I need to write a shell script. The script will take a single command line argument which will be a directory path.
The script will then read each of the files in that directory and output it to standard output; the output will be in HTML and will be a table.
The files will be in this format:
owner sysadmin group
admin ajr
loc S-309
ser 18r97
comment noisy fan
What I have so far:
PATH=/bin:/usr/bin
cd "$#"
if [ test $? -ne 0]
then
exit 1
fi
filenames=$(ls "$#")
for i in $filenames
do
while read item value
do
if [ $item="owner" ] || [ $item="admin" ] || [ $item="loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < i
done
echo '<html>'
echo '<body>'
echo '<table border=1>'
echo '<tr><th>hostname</th><th>location</th><th>Admin</th><th>Serial Number</th><th>owner</th><tr>'
for i in filename
do
echo '<tr><td>'$i'</td><td>'${i[loc]}'</td><td>${i[admin]}'</td><td>'${i[ser]}'</td><td>'${i[owner]}'</td><tr>'
done
echo
echo '</table>'
echo '</body>'
echo '</html>'
The HTML isn't my main concern since I am just following a format given, with each of the values going in between. However, I am getting an error that I have no idea why:
invrep: line 10: i: no such file or directory
but I am using it in a loop. Why is it giving me this error?
Also to confirm, the directory that I used exists; I'm not sure if that has to do with anything though.
Caveat Lector: the code in the question has been edited. The code I commented on may not be the code you can see.
Not directly the problem (chepner diagnosed that in his comment), but:
cd "$#"
if [ test $? -ne 0]
then
exit 1
fi
has a variety of problems. You don't verify that there's only one argument, and you pass all the arguments that are given to cd, which may just quietly ignore the surplus. The test line should use either [ or test but not both. If you use [, the last argument must be ] so you're missing a space:
if test $? -ne 0
if [ $? -ne 0 ]
However, you could short circuit that paragraph by:
cd "$#" || exit 1
(or you could drop the 1 even, though I'd leave it there).
You might want to consider:
case $# in
1) cd "$1" || exit 1;;
*) echo "Usage: $0 directory" >&2; exit 1;;
esac
This verifies that a single argument was passed and that it names a directory you can cd to.
Your looping code also has problems. The while loop should be redirected from "$i" once you've fixed things up:
filenames=$(ls "$#")
for i in $filenames
do
while read item value
do
if [ $item="owner" ] || [ $item="admin" ] || [ $item="loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < $i
# Print HTML here!! Not after this loop
done
Your HTML loop has a lot of problems too — notably using $i as an array instead of $a.
PATH=/bin:/usr/bin
case $# in
1) cd "$1" || exit 1;;
*) echo "Usage: $0 directory" >&2; exit 1;;
esac
echo '<html>'
echo '<body>'
echo '<table border=1>'
echo '<tr><th>hostname</th><th>location</th><th>Admin</th><th>Serial Number</th><th>owner</th><tr>'
filenames=$(ls "$#")
for i in $filenames
do
while read item value
do
if [ $item="owner" ] || [ $item="admin" ] || [ $item="loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < $i
echo "<tr><td>$i</td><td>${a[loc]}</td><td>${a[admin]}</td><td>${a[ser]}</td><td>${a[owner]}</td><tr>"
done
echo '</table>'
echo '</body>'
echo '</html>'
And that still doesn't fix the problem with using ls to generate a list of file names. For that, given the rest of the script, lose filenames altogether and use for file in * instead. You then need to quote $i in the I/O redirection, too.
PATH=/bin:/usr/bin
case $# in
1) cd "$1" || exit 1;;
*) echo "Usage: $0 directory" >&2; exit 1;;
esac
echo '<html>'
echo '<body>'
echo '<table border=1>'
echo '<tr><th>hostname</th><th>location</th><th>Admin</th><th>Serial Number</th><th>owner</th><tr>'
for i in *
do
while read item value
do
if [ $item = "owner" ] || [ $item = "admin" ] ||
[ $item = "loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < "$i"
echo "<tr><td>$i</td><td>${a[loc]}</td><td>${a[admin]}</td><td>${a[ser]}</td><td>${a[owner]}</td><tr>"
done
echo '</table>'
echo '</body>'
echo '</html>'
(Also fixed spacing in the if statement in the loops. The code is still not very elegant, but it is somewhat related to the original code.)
filenames=$(ls "$#")
is wrong, and should never be used by anyone. See the first entry in http://mywiki.wooledge.org/BashPitfalls, or the entire page http://mywiki.wooledge.org/ParsingLs.
If your argument list is a set of directories, the inner loop would look more like this:
declare -A a
for dir in "$#"; do
for i in "$dir/"*; do
while read -r item value; do
case $item in
owner|admin|loc|ser)
a[$item]=$value
;;
esac
done <"$i"
done
done
I'm writing my first bash script and having trouble assigning a file path to a variable:
$target="/etc/httpd/conf/httpd.conf"
It seems bash wants to interpret this with the "=" assignment operator resulting in the script throwing an error to the effect "No such file or directory."
Is there an easy way to do this? I've discovered I can assign a full path to a constant like this:
readonly TARGET=/etc/httpd/conf/httpd.conf
but that seems rather cumbersome. How would I perform string ops to modify/manipulate?
I've also discovered I can put full paths in an array like this:
declare -a cfile=('/root/.bashrc' '/etc/fstab')
All well and good, but how do I assign a file path to a variable?
== == == ==
finished! my first bash script - a basic config file manager
#!/bin/bash
# cfmgr.sh - configuration file manager bash script
# options: -get, -put
# '-get' creates SOURCEDIR/USERDIR and copies config files to USERDIR
# '-put' copies files in SOURCEDIR/USERDIR to system-defined locations on server
# purpose: helps with moving LAMP VMs to different hosts, bulk edits of
# of config files in editors like Notepad++, and backing up config files.
readonly SOURCEDIR=/usr/bin/_serverconfig
while [[ $# > 0 ]]
do
arg="$1"
shift
case $arg in
-put)
put=true
;;
-get)
get=true
;;
*)
badarg=true
;;
esac
done
clear
if [ $badarg ]; then
echo "Invalid argument. Use either 'scf.sh -put' or 'scf.sh -get' to put"\
"or get config files."
exit
elif [ $get ]; then
echo "Enter directory name to store files cfmgr will GET from this server:"
elif [ $put ]; then
echo "Enter directory name containing files cfmgr will PUT to this server:"
else
echo "Use either 'scf.sh -put' or 'scf.sh -get' to put or get config files."
exit
fi
read -e -i $SOURCEDIR"/" USERDIR
pattern=" |'"
if [[ $USERDIR =~ $pattern ]]; then
echo "Spaces not allowed. Please try again."
exit
fi
declare -a cfile=('/root/.bashrc' '/etc/fstab' '/etc/hosts' '/etc/networks'\
'/etc/php.ini' '/etc/nsswitch.conf' '/etc/ntp.conf' '/etc/resolv.conf'\
'/etc/sysctl.conf' '/etc/httpd/conf/httpd.conf' '/etc/selinux/config'\
'/etc/samba/smb.conf' '/etc/samba/smbusers' '/etc/security/limits.conf'\
'/etc/sysconfig/network' '/etc/sysconfig/network-scripts/ifcfg-eth0'\
'/etc/sysconfig/network-scripts/ifcfg-eth1')
if [ $get ]; then
if [[ -d "$USERDIR" ]]; then
echo $USERDIR "directory already exists. Please try again."
exit
else
mkdir -m 755 $USERDIR
fi
for file in ${cfile[#]}
do
if [ -e $file ]; then
rsync -q $file $USERDIR
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$file"\n"
fi
else
printf "not found: "$file"\n"
fi
done
elif [ $put ]; then
if [[ ! -d "$USERDIR" ]]; then
echo $USERDIR "directory does not exist. Please try again."
exit
fi
id=0
cd $USERDIR
for item in *
do
if [[ -f $item ]]; then
cdir[$id]=$item
id=$(($id+1))
fi
done
for file in ${cdir[#]}
do
case $file in
.bashrc)
idx=0
;;
fstab)
idx=1
;;
hosts)
idx=2
;;
networks)
idx=3
;;
php.ini)
idx=4
;;
nsswitch.conf)
idx=5
;;
ntp.conf)
idx=6
;;
resolv.conf)
idx=7
;;
sysctl.conf)
idx=8
;;
httpd.conf)
idx=9
;;
config)
idx=10
;;
smb.conf)
idx=11
;;
smbusers)
idx=12
;;
limits.conf)
idx=13
;;
network)
idx=14
;;
ifcfg-eth0)
idx=15
;;
ifcfg-eth1)
idx=16
;;
*)
printf "not found: "$file"\n"
continue
esac
target=${cfile[$idx]}
if [[ -e $target ]]; then
dtm=$(date +%Y-%m-%d)
mv $target $target"."$dtm
fi
source=$USERDIR"/"$file
dos2unix -q $source
rsync -q $source $target
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$target"\n"
fi
done
read -p "reboot now? (y|n)" selection
case $selection in
[Yy]*)
`reboot`
;;
*)
exit
;;
esac
fi
exit 0
Instead of
$target="/etc/httpd/conf/httpd.conf"
Use:
target="/etc/httpd/conf/httpd.conf"
When bash sees the former, it first substitutes in for "$target". If target was empty, then the line that bash tries to execute, after the variable substitution and quote removal steps, is:
=/etc/httpd/conf/httpd.conf
Since there is no file named "=/etc/httpd/conf/httpd.conf", bash returns with a "No such file or directory" error.
I am trying to write code to check if any argument (on position N) is equal to "--check" and, if its true, require that next argument (position N+1) is present. Otherwise, exit.
How can i achieve that?
i am trying sth like this but it doesnt seem to work:
i am reiterating arguments and if "--check" is found then setting FLAG to 1 which triggers another conditional check for nextArg:
FLAG=0
for i in "$#"; do
if [ $FLAG == 1 ] ; then
nextARG="$i"
FLAG=0
fi
if [ "$i" == "--check" ] ; then
FLAG=1
fi
done
if [ ! -e $nextARG ] ; then
echo "nextARG not found"
exit 0
fi
I would go with getopts. The link shows an example how you could check for your missing parameter.
You could use a form like this. I use it as a general approach when parsing arguments. And I find it less confusing than using getopts.
while [[ $# -gt 0 ]]; do
case "$1" in
--option)
# do something
;;
--option-with-arg)
case "$2" in)
check_pattern)
# valid
my_opt_arg=$2
;;
*)
# invalid
echo "Invalid argument to $1: $2"
exit 1
;;
esac
# Or
if [[ $# -ge 2 && $2 == check_pattern ]]; then
my_opt_arg=$2
else
echo "Invalid argument to $1: $2"
exit 1
fi
shift
;;
*)
# If we don't have default argument types like files. If that is the case we could do other checks as well.
echo "Invalid argument: $1"
# Or
case $1 in
/*)
# It's a file.
FILES+=("$1")
;;
*)
# Invalid.
echo "Invalid argument: $1"
exit 1
;;
esac
esac
shift
done