Bash script errors: i in for loop gives 'no such file or directory' - bash

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

Related

Check parameters of script in bash

I wanna write a script which check variables of this script.
I have tried some, but it isn't working. The idea is:
If on of the parameters is a number, print that it is number
If on of the parameters is a character, print that it is character
If 'man parameter' is executable, print that it is might be a function
Script I have tried:
#!/bin/bash
echo Hello $LOGNAME'!'
test $# -eq 0 && echo 'Try again, no parameters were entered' || echo 'Num of parameters is '$#
re='^[0-9]+$'
for i in $*
do
if ![["$i" =~ $re]];then
echo 'Parameter '$i' is alphabetical'
else
if [["$i" =~ $re]];then
echo 'Parameter '$i' is digital'
else
if [ $i];then
echo $i' might be a function. Try to use man of --help'
fi
fi
fi
done
#!/bin/bash
echo "Hello ${LOGNAME}!"
[ "$#" -eq 0 ] && { echo 'Try again, no parameters were entered'; exit 1; }
echo 'Num of parameters is '$#
re='^[0-9]+$'
for i in "$#"
do
if ! [[ "$i" =~ $re ]];then
echo "Parameter '$i' is alphabetical"
man "$i" > /dev/null 2> /dev/null
if [ "$?" -eq 0 ];then
echo "$i might be a function. Try to use man of --help"
fi
else
echo "Parameter '$i' is digital"
fi
done;
When you write a test you need spaces around your brackets.
You can easily find and fix those bugs if you use shellcheck

Bash script launches the first switch-case loop option again

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
;;

i don't know if my shell script is correct

I have a homework using for loop but I'm not quite understand the task that I have to do in there. I wrote a script but I feel like it's not a correct script. Please help!
Here is the question:
Write a shell script to list out the contents of any directory, and indicate for each file (including invisible ones) whether the file is a directory, a plain file, and whether it is public and/or executable to this process
#!/bin/bash
if [ $# -lt 1 ] ; then
echo " file doesn't exist"
echo
echo " variable needed to run a command"
fi
echo ---------------------------------------------
echo ---------------------------------------------
for i in $*
do
if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
echo ------------------------------------------
if [ -x $i ]; then
echo "executable"
echo "THIS IS A LIST OF EXECUTABLE FILE IN $i"
ls -x $i
fi
echo -----------------------------------------
if [ -r $i ]; then
echo "this file is a public file"
else "this is a private file"
fi
#!/bin/bash
if [ $# -lt 1 ] ; then
echo " file doesn't exist"
echo
echo " variable needed to run a command"
fi
echo ---------------------------------------------
echo ---------------------------------------------
for i in $*
do
if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
echo ------------------------------------------
if [ -x $i ]; then
echo "executable"
echo "THIS IS A LIST OF EXECUTABLE FILE IN $i"
ls -x $i
fi
echo -----------------------------------------
if [ -r $i ]; then
echo "this file is a public file"
else "this is a private file"
fi
Poorly written specifications are the bane of education. "Public" sounds like the wrong word here. I'll assume it means "readable".
You check if there's an argument, but you don't exit the program if there is not. I'd also confirm it's a directory, and readable.
The manual will do you a lot of good. Expect to do a lot of reading till you learn this stuff, and then reference it a lot to be sure.
Read this section carefully, create some tests for yourself to prove they work and that you understand them, and your job will be more than half done.
Don't use [. Generally it's just better to always use [[ instead, unless you are using (( or case or some other construct.
I don't see that a for loop was specified, but it ought to be fine. Just be aware that you might have to specify $1/* and $1/.* separately.
Put all your tests in one loop, though. For each file, test for whether it's a directory - if it is, report it. Test if it's a plain file - if it is, report it.
I do NOT like doing homework for someone, but it looks like you could use an example that simplifies this. I recommend you not use this as written - break it out and make it clearer, but this is a template for the general logic.
#! /bin/env bash
(( $# )) && [[ -d "$1" ]] && [[ -r "$1" ]] || {
echo "use: $0 <dir>" >&2
exit 1
}
for e in "$1"/.* "$1"/*
do echo "$e:"
[[ -d "$e" ]] && echo " is a directory"
[[ -f "$e" ]] && echo " is a plain file"
[[ -r "$e" ]] && echo " is readable"
[[ -x "$e" ]] && echo " is executable"
done
If you read the links I provided you should be able to break this apart and understand it.
Generally, your script is long and a bit convoluted. Simpler is easier to understand and maintain. For example, be very careful about block indentation to understand scope.
$: for i in 1 2 3
> do echo $i
> done
1
2
3
$: echo $i
3
Compare this to -
for i in $*
do if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
You are testing each entry to see if it is a file, and if it is, reporting "THIS IS A LIST OF FILE and DIRECTORY in $i" every time...
but then only testing the last one to see if it's a directory, because the [ -d $i ] is after the done.
...did you run this somewhere to try it, and look at the results?

How to check if multiple variables are defined or not in bash

I want to check, if multiple variable are set or not, if set then only execute the script code, otherwise exit.
something like:
if [ ! $DB=="" && $HOST=="" && $DATE=="" ]; then
echo "you did not set any variable"
exit 1;
else
echo "You are good to go"
fi
You can use -z to test whether a variable is unset or empty:
if [[ -z $DB || -z $HOST || -z $DATE ]]; then
echo 'one or more variables are undefined'
exit 1
fi
echo "You are good to go"
As you have used the bash tag, I've used an extended test [[, which means that I don't need to use quotes around my variables. I'm assuming that you need all three variables to be defined in order to continue. The exit in the if branch means that the else is superfluous.
The standard way to do it in any POSIX-compliant shell would be like this:
if [ -z "$DB" ] || [ -z "$HOST" ] || [ -z "$DATE" ]; then
echo 'one or more variables are undefined'
exit 1
fi
The important differences here are that each variable check goes inside a separate test and that double quotes are used around each parameter expansion.
If you are ok with writing a function for this purpose, it can be pretty convenient.
This solution uses the ${!VAR_NAME} syntax to check whether the variable is empty and has the added benefit of telling you which variable names are empty.
check_vars()
{
var_names=("$#")
for var_name in "${var_names[#]}"; do
[ -z "${!var_name}" ] && echo "$var_name is unset." && var_unset=true
done
[ -n "$var_unset" ] && exit 1
return 0
}
# Usage for this case
check_vars DB HOST DATE
echo "You are good to go"
I wound up using variable-variables to loop through an easily managed HEREDOC list of variable names:
# Ensure non-empty values.
# Loop through HEREDOC, test variable-variable isn't blank.
while read var; do
[ -z "${!var}" ] && { echo "$var is empty or not set. Exiting.."; exit 1; }
done << EOF
KUBE_NAMESPACE
DOCKER_REGISTRY
DOCKER_DEPLOY_USER
DOCKER_DEPLOY_PASSWORD
DOCKER_DEPLOY_EMAIL
EOF
You can check it also by put the variables name in a file
DB=myDB
HOST=myDB
DATE=myDATE
then test them if currently empty or unset
#!/bin/bash
while read -r line; do
var=`echo $line | cut -d '=' -f1`
test=$(echo $var)
if [ -z "$(test)" ]; then
echo 'one or more variables are undefined'
exit 1
fi
done <var.txt
echo "You are good to go"
Nice solution from #joe.still !
improvement is to exit after checking all variables
i=0
while read var; do
[ -z "${!var}" ] && { echo "$var is empty or not set. Exiting.."; let i=i+1; }
done << EOF
KUBE_NAMESPACE
DOCKER_REGISTRY
DOCKER_DEPLOY_USER
DOCKER_DEPLOY_PASSWORD
DOCKER_DEPLOY_EMAIL
EOF
if [ $i -gt 0 ]; then
echo $i
echo "exiting"
exit 1
fi
Good Day Everyone.
I've personally used this method in my bash scripts. Verified works on bash 4.4 and later in Ubuntu, openSUSE, and ClearLinux.
Can RHEL|CentOS|Alma and Arch Based users let me know it it works fine for you?
( [ "$VAR1""$VAR2""$VAR3""$VAR4""$VAR5" ] && echo -e " Warning: StackIsNotClear" ) || { echo -e " GoodNews: StackIsClear"; }

How can I loop If Statements in Bash

Here is my code:
#!/bin/bash
echo "Letter:"
read a
if [ $a = "a" ]
then
echo "LOL"
fi
if [ $a = "b" ]
then
echo "ROFL"
fi
Is there a way for me to loop this so that, after displaying either LOL or ROFL, I would be asked for a letter again?
Yes.
Oh, you want to know how?
while true; do
echo "Letter:"
read a
if [ $a = "a" ]
then
echo "LOL"
elif [ $a = "b" ]
then
echo "ROFL"
fi
done
Of course, you probably want some way to get out of that infinite loop. The command to run in that case is break. I would write the whole thing like this:
while read -p Letter: a; do
case "$a" in
a) echo LOL;;
b) echo ROFL;;
q) break;;
esac
done
which lets you exit the loop either by entering 'q' or generating end-of-file (control-D).
Don't forget that you always want -r flag with read.
Also there is a quoting error on that line:
if [ $a = "a" ] # this will fail if a='*'
So here is a bit better version(I've also limited the user to input only 1 character):
#!/bin/bash
while true; do
read -rn1 -p 'Letter: ' a
echo
if [[ $a = 'a' ]]; then
echo "LOL"
elif [[ $a = 'b' ]]; then
echo "ROFL"
else
break
fi
done
Or with switch statement:
#!/bin/bash
while read -rn1 -p 'Letter: ' a; do
echo
case $a in
a) echo LOL;;
b) echo ROFL;;
*) break;;
esac
done

Resources