WHILE loop not looping - linux

I'm fairly new to Linux and shell scripting.
My problem is, that the script should read 2 tokens from a file called "list" - using these tokens, it creates a user and depending on the second token, a sub folder. It does this just fine - but only once. Only ONCE. Is there a problem with my WHILE loop?
Here is a few sample lines from "list":
egyes n
kettes y
harmas y
Here's the script:
#!/bin/bash
echo " " >> /root/userpass.txt
most=$(date)
while read user rr; do
p1=${user:0:2}
p2=${user:3:4}
pass=$p1$RANDOM$p2
echo $user - $pass --" LÉTREHOZVA: "$most >> /root/userpass.txt
adduser $user > /dev/null
echo $user:$pass | chpasswd > /dev/null
uhome=/home/$user
if [ $rr=="y" ]; then
mkdir $uhome/rockandroll
chown $user $uhome/rockandroll
fi
mkdir $uhome/res-devres
chown $user $uhome/res-devres
ftpc=/etc/proftpd/proftpd.conf
echo "#"$1 >> $ftpc
echo "<Directory "$uhome"/res-devres/>" >> $ftpc
echo ' <Limit CDUP XCUP STOR LIST CWD XCWD STOU>' >> $ftpc
echo ' AllowAll' >> $ftpc
echo ' </Limit>' >> $ftpc
echo ' <Limit RETR DELE>' >> $ftpc
echo ' DenyAll' >> $ftpc
echo ' </Limit>' >> $ftpc
echo '</Directory>' >> $ftpc
echo " " >> $ftpc
echo " "
done < list
Thanks in advance.

change from
if [ $rr=="y" ]; then
to
if [ $rr == "y" ]; then

As pointed out in the comments, some command in your loop is reading from standard input. You can either figure out which command that is, and redirect its standard input from /dev/null:
bad_command < /dev/null
or simply use a different file descriptor for the while loop:
while read user rr <&3; do
...
done 3< list
Now the read command is not reading from standard input, but from file descriptor 3, which is unlikely to be in use by any command in the body of the loop.
As pointed out by BMW, you need to fix your if statement:
if [ "$rr" = "y" ]; then
The spaces around the equal sign are necessary, as [ is a command, not part of the if syntax, and it requires 3 distinct arguments ($rr, =, and "y"); it will not parse the single string $rr="y" as a comparison. = is preferred with the [ command, as generally == is not the POSIX equality comparison operator. However, bash does allow ==, but also provides a superior command which does not require $rr to be quoted as required for safety with [:
if [[ $rr == y ]]; then # == or = will work the same
You can save some typing in the last section of your loop by combining the echo statements into a single compound command and redirecting their combined output once:
{
echo ...
echo ...
echo ...
} > "$ftpc"
Another option as pointed out by tripleee, requires only a single call to cat. It spawns an external process, but looks cleaner.
cat > "$ftpc" <<EOF
#$1
<Directory $uhome/res-devres/>
etc
EOF
You could also just echo and a single string with embedded newlines.
echo "#$1
<Directory $uhome/res-devres/>
etc
" > "$ftpc"

Related

How to use lines as a selectable function in shell script?

I'm currently creating a script to retrieve a set of lines from a file and will let users select any one of them to perform certain task.
Eg:
echo "my_files"
1) my_files1
2) my_files2
3) my_files3
...
n) my_filesN
I want to let the user select any one of these line and perform certain task based on the selection in shell script.
Perhaps you want to have something like the next menu:
#!/bin/bash
test -f "$1" || { echo "File \"$1\" can not be found."; exit 1; }
readarray -t options < "${1}"
# set the prompt used by select, replacing "#?"
PS3="Use a number to select a command or 'q' to cancel: "
stopmenu=
while [[ -z "${stopmenu}" ]]; do
select opt in "${options[#]}" ; do
if [[ ${REPLY} = q ]]; then
stopmenu=1
break
fi
if [[ ${#opt} -gt 0 ]]; then
echo "$opt"
source <(echo "${opt}")
fi
break
done
echo
done
You can give a filename with shell commands as an argument, something like menu myfile. The file with menu options can look like
echo "Hello world"
echo "Comment is allowed" # Just add text after a hash.
ls -l
echo "Value of xxx=[${xxx}]" # xxx can be filled in the next menu option
read -p "Enter value for xxx: " xxx # The variable xxx will be known in the menu
Read the filenames into an array with readarray.
Use the select built-in to display the list of files as a menu.
echo "Select a file":
readarray -t files list_of_files.txt
select file in "${files[#]}"; do
echo "You selected $file";
done

Improve Bash script to obtain file names, remove part of their names, and display the result until a key is entered

I have fully working code that obtains a list of file names from the '$testDir', removes the final 3 characters from each, runs a different script using the adjusted names, and displays a list of the edited names that had errors when used in the seperate script, until the letter 'q' is entered by the user.
Here is the code:
#!/bin/bash
# Declarations
declare -a testNames
declare -a errorNames
declare -a errorDescription
declare resultDir='../Results'
declare outputCheck=''
declare userEntry=''
declare -i userSelect
# Obtain list of files in $resultDir and remove the last 3 chars from each file name
for test in `ls $resultDir`; do
testNames+=("${test::-3}")
done
# Run 'checkFile.sh' script for each adjusted file name and add name and result to apporopriate arrays if 'checkFile.sh' script fails
for f in "${testNames[#]}"; do
printf '%s' "$f: "
outputCheck=$(./scripts/checkFile.sh -v "${f}" check)
if [[ $outputCheck != "[0;32mNo errors found.[0m" ]];
then
errorNames+=("$f")
errorDescription+=("$outputCheck")
echo -e '\e[31mError(s) found\e[0m'
else
printf '%s\n' "$outputCheck"
fi
done
#Prompts user to save errors, if any are present
if [ "${#errorNames[#]}" != 0 ];
then
until [[ $userEntry = "q" ]]; do
echo "The following tests had errors:"
for(( i=1; i<=${#errorNames[#]}; i++ )); do
echo -e $i: "\e[31m${errorNames[i-1]}\e[0m"
done
echo "Enter the corresponding number in the list to save the errors found or enter 'q' to quit"
read -r userEntry
numInput=$userEntry
if [ $numInput -gt 0 -a $numInput -le ${#errorNames[#]} ];
then
mkdir -p ./Errors
echo -e "File" "\e[96m${errorNames[$userEntry-1]}""_Error_Info.txt\e[0m created in the 'Errors' folder which contains details of the error(s)"
echo "${errorDescription[$userEntry-1]}" > "./Errors/""${errorNames[$userEntry-1]}""_Error_Info.txt"
fi
done
echo 'Successfully Quit'
exit $?
fi
echo 'No errors found'
exit $?
As someone who is new to Bash and programming concepts, I would really appreciate any suggested improvements to this code. In particular, the script needs to run quickly as the 'checkFile.sh' script takes a long time to run itself. I have a feeling the way I have written the code is not concise either.

Writing output into a file via linux script?

I have to write a script in linux that saves one line of text to a file and then appends a new line. What I currently have is something like:
read "This line will be saved to text." Text1
$Text1 > $Script.txt
read "This line will be appended to text." Text2
$Text2 >> $Script.txt
One of the main benefits of scripting is that you can automate processes. Using
read like you have destroys that. You can accept input from the user without
losing automation:
#!/bin/sh
if [ "$#" != 3 ]
then
echo 'script.sh [Text1] [Text2] [Script]'
exit
fi
printf '%s\n' "$1" "$2" > "$3"
Assuming you don't mind if the second line of your output file is overwritten (not appended) every time the script is run; this might do.
#!/bin/sh
output_file=output.dat
if [ -z "$1" ] || [ -z "$2" ]; then echo Need at least two arguments.; fi
line1=$1; line2=$2
echo $line1 > $output_file
echo $line2 >> $output_file
Executing the script:
# chmod +x foo.sh
# ./foo.sh
Need at least two arguments.
# ./foo.sh hello world
# cat output.dat
hello
world

Unexpected Operator when piping file into cut

I'm trying to check the type of a file using file, and then using the cut command to get only the type such as JPEG in my case, and then using it to check whether or not the file is the desired type. However whenever I run this in Shell it spits out pic1.jpg: Unexpected Operator. I'm not sure where the problem is and it's been boggling me for a while now.
!#/bin/sh
file=$(file -F " " $1)
if [ $file = ERROR: ] || [ $file = empty ] then
echo "$1 is not a valid jpeg file." >&2 >> error.log
else
extension=$(file -F " " $1 | cut -f 3 -d " ")
Note that you'll have to enclose $file in double quotes in the if statement. Otherwise the contents of $file will be interpreted as commands by the shell. This is one of the major pitfalls of shell coding you should be aware of.
In addition you where missing the ; between the ] and the then statements. Note that [ ... ] is the same as the test command. ] is just the last argument to it. Not 'just a bracket' like you would expect in 'normal languages' :). So, if there are two expressions on the same line the will have to be separated by an ;
Although I could suggest some enhancements to the I kept my example as much the same as yours to point you to the error. The following code will work:
#!/bin/sh
file=$(file -F " " $1)
if [ "$file" = "ERROR:" ] || [ "$file" = "empty" ] ; then
echo "$1 is not a valid jpeg file." 1>&2 >> error.log
else
extension=$(file -F " " $1 | cut -f 3 -d " ")
fi
echo "$extension"
Note that without double quotes your script would lead to the following if statement:
if [ test.sh POSIX shell script, ASCII text executable = Error: ] || [ test.sh POSIX shell script, ASCII text executable = empty ]

How do I know the script file name in a Bash script?

How can I determine the name of the Bash script file inside the script itself?
Like if my script is in file runme.sh, then how would I make it to display "You are running runme.sh" message without hardcoding that?
me=`basename "$0"`
For reading through a symlink1, which is usually not what you want (you usually don't want to confuse the user this way), try:
me="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
IMO, that'll produce confusing output. "I ran foo.sh, but it's saying I'm running bar.sh!? Must be a bug!" Besides, one of the purposes of having differently-named symlinks is to provide different functionality based on the name it's called as (think gzip and gunzip on some platforms).
1 That is, to resolve symlinks such that when the user executes foo.sh which is actually a symlink to bar.sh, you wish to use the resolved name bar.sh rather than foo.sh.
# ------------- SCRIPT ------------- #
#!/bin/bash
echo
echo "# arguments called with ----> ${#} "
echo "# \$1 ----------------------> $1 "
echo "# \$2 ----------------------> $2 "
echo "# path to me ---------------> ${0} "
echo "# parent path --------------> ${0%/*} "
echo "# my name ------------------> ${0##*/} "
echo
exit
# ------------- CALLED ------------- #
# Notice on the next line, the first argument is called within double,
# and single quotes, since it contains two words
$ /misc/shell_scripts/check_root/show_parms.sh "'hello there'" "'william'"
# ------------- RESULTS ------------- #
# arguments called with ---> 'hello there' 'william'
# $1 ----------------------> 'hello there'
# $2 ----------------------> 'william'
# path to me --------------> /misc/shell_scripts/check_root/show_parms.sh
# parent path -------------> /misc/shell_scripts/check_root
# my name -----------------> show_parms.sh
# ------------- END ------------- #
With bash >= 3 the following works:
$ ./s
0 is: ./s
BASH_SOURCE is: ./s
$ . ./s
0 is: bash
BASH_SOURCE is: ./s
$ cat s
#!/bin/bash
printf '$0 is: %s\n$BASH_SOURCE is: %s\n' "$0" "$BASH_SOURCE"
$BASH_SOURCE gives the correct answer when sourcing the script.
This however includes the path so to get the scripts filename only, use:
$(basename $BASH_SOURCE)
If the script name has spaces in it, a more robust way is to use "$0" or "$(basename "$0")" - or on MacOS: "$(basename \"$0\")". This prevents the name from getting mangled or interpreted in any way. In general, it is good practice to always double-quote variable names in the shell.
If you want it without the path then you would use ${0##*/}
To answer Chris Conway, on Linux (at least) you would do this:
echo $(basename $(readlink -nf $0))
readlink prints out the value of a symbolic link. If it isn't a symbolic link, it prints the file name. -n tells it to not print a newline. -f tells it to follow the link completely (if a symbolic link was a link to another link, it would resolve that one as well).
I've found this line to always work, regardless of whether the file is being sourced or run as a script.
echo "${BASH_SOURCE[${#BASH_SOURCE[#]} - 1]}"
If you want to follow symlinks use readlink on the path you get above, recursively or non-recursively.
The reason the one-liner works is explained by the use of the BASH_SOURCE environment variable and its associate FUNCNAME.
BASH_SOURCE
An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}.
FUNCNAME
An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.
This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, ${FUNCNAME[$i]} was called from the file ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin displays the current call stack using this information.
[Source: Bash manual]
Since some comments asked about the filename without extension, here's an example how to accomplish that:
FileName=${0##*/}
FileNameWithoutExtension=${FileName%.*}
Enjoy!
These answers are correct for the cases they state but there is a still a problem if you run the script from another script using the 'source' keyword (so that it runs in the same shell). In this case, you get the $0 of the calling script. And in this case, I don't think it is possible to get the name of the script itself.
This is an edge case and should not be taken TOO seriously. If you run the script from another script directly (without 'source'), using $0 will work.
Re: Tanktalus's (accepted) answer above, a slightly cleaner way is to use:
me=$(readlink --canonicalize --no-newline $0)
If your script has been sourced from another bash script, you can use:
me=$(readlink --canonicalize --no-newline $BASH_SOURCE)
I agree that it would be confusing to dereference symlinks if your objective is to provide feedback to the user, but there are occasions when you do need to get the canonical name to a script or other file, and this is the best way, imo.
this="$(dirname "$(realpath "$BASH_SOURCE")")"
This resolves symbolic links (realpath does that), handles spaces (double quotes do this), and will find the current script name even when sourced (. ./myscript) or called by other scripts ($BASH_SOURCE handles that). After all that, it is good to save this in a environment variable for re-use or for easy copy elsewhere (this=)...
You can use $0 to determine your script name (with full path) - to get the script name only you can trim that variable with
basename $0
if your invoke shell script like
/home/mike/runme.sh
$0 is full name
/home/mike/runme.sh
basename $0 will get the base file name
runme.sh
and you need to put this basic name into a variable like
filename=$(basename $0)
and add your additional text
echo "You are running $filename"
so your scripts like
/home/mike/runme.sh
#!/bin/bash
filename=$(basename $0)
echo "You are running $filename"
This works fine with ./self.sh, ~/self.sh, source self.sh, source ~/self.sh:
#!/usr/bin/env bash
self=$(readlink -f "${BASH_SOURCE[0]}")
basename=$(basename "$self")
echo "$self"
echo "$basename"
Credits: I combined multiple answers to get this one.
echo "$(basename "`test -L ${BASH_SOURCE[0]} \
&& readlink ${BASH_SOURCE[0]} \
|| echo ${BASH_SOURCE[0]}`")"
In bash you can get the script file name using $0. Generally $1, $2 etc are to access CLI arguments. Similarly $0 is to access the name which triggers the script(script file name).
#!/bin/bash
echo "You are running $0"
...
...
If you invoke the script with path like /path/to/script.sh then $0 also will give the filename with path. In that case need to use $(basename $0) to get only script file name.
Short, clear and simple, in my_script.sh
#!/bin/bash
running_file_name=$(basename "$0")
echo "You are running '$running_file_name' file."
Out put:
./my_script.sh
You are running 'my_script.sh' file.
Info thanks to Bill Hernandez. I added some preferences I'm adopting.
#!/bin/bash
function Usage(){
echo " Usage: show_parameters [ arg1 ][ arg2 ]"
}
[[ ${#2} -eq 0 ]] && Usage || {
echo
echo "# arguments called with ----> ${#} "
echo "# \$1 -----------------------> $1 "
echo "# \$2 -----------------------> $2 "
echo "# path to me ---------------> ${0} " | sed "s/$USER/\$USER/g"
echo "# parent path --------------> ${0%/*} " | sed "s/$USER/\$USER/g"
echo "# my name ------------------> ${0##*/} "
echo
}
Cheers
DIRECTORY=$(cd `dirname $0` && pwd)
I got the above from another Stack Overflow question, Can a Bash script tell what directory it's stored in?, but I think it's useful for this topic as well.
Here is what I came up with, inspired by Dimitre Radoulov's answer (which I upvoted, by the way).
script="$BASH_SOURCE"
[ -z "$BASH_SOURCE" ] && script="$0"
echo "Called $script with $# argument(s)"
regardless of the way you call your script
. path/to/script.sh
or
./path/to/script.sh
$0 will give the name of the script you are running. Create a script file and add following code
#!/bin/bash
echo "Name of the file is $0"
then run from terminal like this
./file_name.sh
To get the "realpath" of script or sourced scripts in all cases :
fullname=$(readlink $0) # Take care of symbolic links
dirname=${fullname%/*} # Get (most of the time) the dirname
realpath=$(dirname $BASH_SOURCE) # TO handle sourced scripts
[ "$realpath" = '.' ] && realpath=${dirname:-.}
Here is the bash script to generate (in a newly created "workdir" subdir and in "mytest" in current dir), a bash script which in turn will source another script, which in turm will call a bash defined function .... tested with many ways to launch them :
#!/bin/bash
##############################################################
ret=0
fullname=$(readlink $0) # Take care of symbolic links
dirname=${fullname%/*} # Get (most of the time) the dirname
realpath=$(dirname $BASH_SOURCE) # TO handle sourced scripts
[ "$realpath" = '.' ] && realpath=${dirname:-.}
fullname_withoutextension=${fullname%.*}
mkdir -p workdir
cat <<'EOD' > workdir/_script_.sh
#!/bin/bash
##############################################################
ret=0
fullname=$(readlink $0) # Take care of symbolic links
dirname=${fullname%/*} # Get (most of the time) the dirname
realpath=$(dirname $BASH_SOURCE) # TO handle sourced scripts
[ "$realpath" = '.' ] && realpath=${dirname:-.}
fullname_withoutextension=${fullname%.*}
echo
echo "# ------------- RESULTS ------------- #"
echo "# path to me (\$0)-----------> ${0} "
echo "# arguments called with ----> ${#} "
echo "# \$1 -----------------------> $1 "
echo "# \$2 -----------------------> $2 "
echo "# path to me (\$fullname)----> ${fullname} "
echo "# parent path(\${0%/*})------> ${0%/*} "
echo "# parent path(\$dirname)-----> ${dirname} "
echo "# my name ----\${0##*/}------> ${0##*/} "
echo "# my source -\${BASH_SOURCE}-> ${BASH_SOURCE} "
echo "# parent path(from BASH_SOURCE) -> $(dirname $BASH_SOURCE)"
echo "# my function name -\${FUNCNAME[0]}------> ${FUNCNAME[0]}"
echo "# my source or script real path (realpath)------------------> $realpath"
echo
[ "$realpath" = "workdir" ] || ret=1
[ $ret = 0 ] || echo "*******************************************************"
[ $ret = 0 ] || echo "*********** ERROR **********************************"
[ $ret = 0 ] || echo "*******************************************************"
show_params () {
echo
echo "# --- RESULTS FROM show_params() ---- #"
echo "# path to me (\$0)-----------> ${0} "
echo "# arguments called with ----> ${#} "
echo "# \$1 -----------------------> $1 "
echo "# \$2 -----------------------> $2 "
echo "# path to me (\$fullname)----> ${fullname} "
echo "# parent path(\${0%/*})------> ${0%/*} "
echo "# parent path(\$dirname)-----> ${dirname} "
echo "# my name ----\${0##*/}------> ${0##*/} "
echo "# my source -\${BASH_SOURCE}-> ${BASH_SOURCE} "
echo "# parent path(from BASH_SOURCE) -> $(dirname $BASH_SOURCE)"
echo "# my function name -\${FUNCNAME[0]}------> ${FUNCNAME[0]}"
echo "# my source or script real path (realpath)------------------> $realpath"
echo
[ "$realpath" = "workdir" ] || ret=1
[ $ret = 0 ] || echo "*******************************************************"
[ $ret = 0 ] || echo "*********** ERROR **********************************"
[ $ret = 0 ] || echo "*******************************************************"
}
show_params "$#"
EOD
cat workdir/_script_.sh > workdir/_side_by_side_script_sourced.inc
cat <<'EOD' >> workdir/_script_.sh
echo "# . $realpath/_side_by_side_script_sourced.inc 'hello there' 'william'"
. $realpath/_side_by_side_script_sourced.inc 'hello there' 'william'
[ $ret = 0 ] || echo "*******************************************************"
[ $ret = 0 ] || echo "*********** ERROR **********************************"
[ $ret = 0 ] || echo "*******************************************************"
EOD
chmod +x workdir/_script_.sh
[ -L _mytest_ ] && rm _mytest_
ln -s workdir/_script_.sh _mytest_
# ------------- CALLED ------------- #
called_by () {
echo '=========================================================================='
echo " Called by : " "$#"
echo '=========================================================================='
eval "$#"
}
called_by bash _mytest_
called_by ./_mytest_
called_by bash workdir/_script_.sh
called_by workdir/_script_.sh
called_by . workdir/_script_.sh
# ------------- RESULTS ------------- #
echo
echo
[ $ret = 0 ] || echo "*******************************************************"
[ $ret = 0 ] || echo "*********** ERROR **********************************"
[ $ret = 0 ] || echo "*******************************************************"
echo
[ $ret = 0 ] && echo ".... location of scripts (\$realpath) should always be equal to $realpath, for all test cases at date".
echo
# ------------- END ------------- #
echo "You are running $0"
somthing like this?
export LC_ALL=en_US.UTF-8
#!/bin/bash
#!/bin/sh
#----------------------------------------------------------------------
start_trash(){
ver="htrash.sh v0.0.4"
$TRASH_DIR # url to trash $MY_USER
$TRASH_SIZE # Show Trash Folder Size
echo "Would you like to empty Trash [y/n]?"
read ans
if [ $ans = y -o $ans = Y -o $ans = yes -o $ans = Yes -o $ans = YES ]
then
echo "'yes'"
cd $TRASH_DIR && $EMPTY_TRASH
fi
if [ $ans = n -o $ans = N -o $ans = no -o $ans = No -o $ans = NO ]
then
echo "'no'"
fi
return $TRUE
}
#-----------------------------------------------------------------------
start_help(){
echo "HELP COMMANDS-----------------------------"
echo "htest www open a homepage "
echo "htest trash empty trash "
return $TRUE
} #end Help
#-----------------------------------------------#
homepage=""
return $TRUE
} #end cpdebtemp
# -Case start
# if no command line arg given
# set val to Unknown
if [ -z $1 ]
then
val="*** Unknown ***"
elif [ -n $1 ]
then
# otherwise make first arg as val
val=$1
fi
# use case statement to make decision for rental
case $val in
"trash") start_trash ;;
"help") start_help ;;
"www") firefox $homepage ;;
*) echo "Sorry, I can not get a $val for you!";;
esac
# Case stop

Resources