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

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

Related

BASH Shell named command line arguments [duplicate]

This question already has answers here:
An example of how to use getopts in bash
(8 answers)
Closed 7 months ago.
Can some please help how to pass named command line arguments to my shell script. Something like below.
./myOwnShellScript.sh -name Thor -tool Hammer
Below is one way to implement
#!/bin/bash
#################################################################################################
# Script name - myOwnShellScript.sh
# Description - This script is used to implement my own logic.
# Author - Tony
#################################################################################################
function usage(){
echo "Here is usage..."
echo "./myOwnShellScript.sh -name <<Name of your hero>> -tool <<your hero's tool>>"
}
function paramMap(){
declare -A params=( ["name"]="name" ["tool"]="tool" )
paramVarName="${params[${1}]}"
[ -z "${paramVarName}" ] && echo "info" || echo "${paramVarName}"
}
####################################################################################
#----------------------------------------------------------------------------------#
# Main starts, the script execution starts here. #
#----------------------------------------------------------------------------------#
####################################################################################
export TERM="xterm"
clear
trap "exit 1" TERM
export TOP_PID=$$
CURR_TIME=$(date +"%Y-%m-%d-%T")
echo -e "\n**********************************************"
echo -e "\n myOwnShellScript.sh Script Started ${CURR_TIME}"
echo -e "\n**********************************************"
##################################################################################################
# Variable Declaration
##################################################################################################
#Read command line parameters and set script variables.
while [ $# -gt 0 ]; do
if [[ $1 == *"-"* ]]; then
v="${1/-/}"
p=$(paramMap ${v})
paramValue=$([ -z "$2" ] && echo "1" || echo "$2")
declare $p="${paramValue}"
fi
shift
done
echo "Here is your hero ${name}"
echo "His tool is ${tool}"

Using sed after cat << 'EOT' to substitute just one variable inside the generated script

I'm building a script for php-fpm compilation, installation and deployment in ubuntu 14. At one point, I have got to generate another file using this main script. The resulting file is a script and should have all variables BUT one NOT expanded.
So I started with cat << 'EOT' in will of resolving the thing after the file generation with sed. But I find myself in a "logic" blackhole.
As for the EOT quoting beeing an issue for expanding just one variable, the same is for the sed line. I went straight writing the following, then laught at it without even executing it, of course.
sed -i 's/\$PhpBuildVer\/$PhpBuildVer' /etc/init.d/php-$PhpBuildVer-fpm
OR
sed -i "s/\$PhpBuildVer\/$PhpBuildVer" /etc/init.d/php-$PhpBuildVer-fpm
both would fail, while I need the first pattern to be the "$PhpBuildVer" itself and the other one beeing the expanded variable, for instance, 7.1.10.
How would I perform this substituion with either sed or another GNU Linux command?
This is my script, most of the parts have been cut-off as non question related.
#!/bin/bash
PhpBuildVer="7.1.10"
... #removed non relevant parts of the script
cat << 'EOT' >> /etc/init.d/php-$PhpBuildVer-fpm
#! /bin/sh
### BEGIN INIT INFO
# Provides: php-$PhpBuildVer-fpm
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts php-$PhpBuildVer-fpm
# Description: starts the PHP FastCGI Process Manager daemon
### END INIT INFO
php_fpm_BIN=/opt/php-$PhpBuildVer/sbin/php-fpm
php_fpm_CONF=/opt/php-$PhpBuildVer/etc/php-fpm.conf
php_fpm_PID=/opt/php-$PhpBuildVer/var/run/php-fpm.pid
php_opts="--fpm-config $php_fpm_CONF"
wait_for_pid () {
try=0
while test $try -lt 35 ; do
case "$1" in
'created')
if [ -f "$2" ] ; then
try=''
break
fi
;;
'removed')
if [ ! -f "$2" ] ; then
try=''
break
fi
;;
esac
echo -n .
try=`expr $try + 1`
sleep 1
done
}
case "$1" in
start)
echo -n "Starting php-fpm "
$php_fpm_BIN $php_opts
if [ "$?" != 0 ] ; then
echo " failed"
exit 1
fi
wait_for_pid created $php_fpm_PID
if [ -n "$try" ] ; then
echo " failed"
exit 1
else
echo " done"
fi
;;
stop)
echo -n "Gracefully shutting down php-fpm "
if [ ! -r $php_fpm_PID ] ; then
echo "warning, no pid file found - php-fpm is not running ?"
exit 1
fi
kill -QUIT `cat $php_fpm_PID`
wait_for_pid removed $php_fpm_PID
if [ -n "$try" ] ; then
echo " failed. Use force-exit"
exit 1
else
echo " done"
echo " done"
fi
;;
force-quit)
echo -n "Terminating php-fpm "
if [ ! -r $php_fpm_PID ] ; then
echo "warning, no pid file found - php-fpm is not running ?"
exit 1
fi
kill -TERM `cat $php_fpm_PID`
wait_for_pid removed $php_fpm_PID
if [ -n "$try" ] ; then
echo " failed"
exit 1
else
echo " done"
fi
;;
restart)
$0 stop
$0 start
;;
reload)
echo -n "Reload service php-fpm "
if [ ! -r $php_fpm_PID ] ; then
echo "warning, no pid file found - php-fpm is not running ?"
exit 1
fi
kill -USR2 `cat $php_fpm_PID`
echo " done"
;;
*)
echo "Usage: $0 {start|stop|force-quit|restart|reload}"
exit 1
;;
esac
EOF
#Here the variable should be substituted.
chmod 755 /etc/init.d/php-$PhpBuildVer-fpm
... #removed non relevant parts of the script
I am not 100% sure, but I think what you are looking for is:
sed -i 's/\$PhpBuildVer/'"$PhpBuildVer"'/' /etc/init.d/php-$PhpBuildVer-fpm
You can actually put two quoted expressions right next to each other in bash. E.g., echo '12'"34"'56' will output 123456. In this case, the first \$PhpBuildVer is in '' so it can match literally, and the second is in "" so that it will be expanded.
(But maybe you should consider using a template file and php, or (blatant plug)
perlpp* to build the script, rather than inlining all the text into your main script. ;) )
Edit by the way, using cat ... >> rather than cat ... > means you will be appending to the script unless you have rmed it somewhere in the code you didn't show.
Edit 2 If $PhpBuildVer has any characters in it that sed interprets in the replacement text, you might need to escape it:
repl_text="$(sed -e 's/[\/&]/\\&/g' <<<"$PhpBuildVer")"
sed -i 's/\$PhpBuildVer/'"$repl_text"'/' /etc/init.d/php-$PhpBuildVer-fpm
Thanks to this answer by Pianosaurus.
Tested example
I put this in make.sh:
#!/bin/bash
f=42 # The variable we are going to substitute
cat <<'EOT' >"test-$f.sh" # The script we are generating
#!/bin/sh
# Provides: test-$f.sh
echo 'Hello, world!'
EOT
echo "test-$f.sh before substitution is:"
echo "---------"
cat "test-$f.sh"
echo "---------"
sed -i 's/\$f/'"$f"'/' "test-$f.sh" # The substitution, from above
echo "test-$f.sh after substitution is:"
echo "---------"
cat "test-$f.sh"
echo "---------"
The output I get is:
test-42.sh before substitution is:
---------
#!/bin/sh
# Provides: test-$f.sh
echo 'Hello, world!'
---------
(note the literal $f)
test-42.sh after substitution is:
---------
#!/bin/sh
# Provides: test-42.sh
echo 'Hello, world!'
---------
(now the $f is gone, and has been replaced with its value, 42)
perlpp example
Since *I am presently the maintainer of perlpp, I'll give you that example, too :) . In a template file that I called test.template, I put:
#!/bin/sh
# Provides: test-<?= $S{ver} ?>.sh
echo 'Hello, world!'
That was exactly the content of the script I wanted, but with <?= $S{ver} ?> where I wanted to do the substitution. I then ran
perlpp -s ver=\'7.1.10\' test.template
(with escaped quotes to pass them to perl) and got the output:
#!/bin/sh
# Provides: test-7.1.10.sh
echo 'Hello, world!'
:)
Any -s name=\'value\' command-line argument to perlpp creates $S{name}, which you can refer to in the template.
<?= expr ?> prints the value of expression expr
Therefore, <?= $S{name} ?> outputs the value given on the command line for name.
Just break up the heredoc. eg
cat > file << 'EOF'
This line will not be interpolated: $FOO
EOF
cat >> file << EOF
and this line will: $FOO
EOF
If for some reason you do want to used sed as well, don't do it after, just use it instead of cat:
sed 's#foo#bar#g' >> file << EOF
this line's foo is changed by sed, with interpolated $variables
EOF

Bash:Else not working in if/else statement in case statement

I am trying to check if a user types multiple arguments in a command line using case and if/else statements. What's wrong is that I keep getting the default case instead of the same command, but with 2 more arguments. For instance, one of my conditions is:
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
prints the first condition, but if I type, say, del aaa bbb, I get the default case, which is:
echo "ERROR: Unrecognized command"
I'm also using this to read a user's input, if that helps.
read -p "wcl> " -r wcl $2 $3
I don't really know if there's a better way to solve this without scrapping all my code and starting from scratch.
This is the full code:
#!/bin/bash
#use read command
echo Welcome to the Windows Command Line simulator!
echo Enter your commands below
while true
do
read -p "wcl> " -r wcl $2 $3
case $wcl in
dir)
echo "using Bash command: ls $2 $3"
ls
continue
;;
copy)
FILE="$2"
if [ "$#" -ne 3 ]
then
echo "Usage: copy sourcefile destinationfile"
else
echo "using Bash command: cp $2 $3"
if [ -f "$FILE" ]
then
cp $2 $3
else
echo "cannot stat $FILE: No such file or directory">&2
fi
echo done
fi
continue
;;
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
continue
;;
move)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: moves a file to another file name and location"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
rename)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: renames a file"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
ipconfig)
ifconfig eth0 | grep "inet addr" | cut -d ':' -f 2 | cut -d ' ' -f 1
continue
;;
exit)
echo "Goodbye"
exit 1
;;
^c)
echo "Goodbye"
exit 1
;;
*)
echo "ERROR: Unrecognized command"
continue
esac
done
You can't use read to set the positional parameters, although it isn't clear why you would need to here. Just use regular parameters.
while true
do
read -p "wcl> " -r wcl arg1 arg2
case $wcl in
dir)
echo "using Bash command: ls $arg1 $arg2"
ls "$arg1" "$arg2"
continue
;;
# ...
esac
done
The way read -r wcl $2 $3 is executed is that $2 and $3 are first expanded to give names that read will use to set variables. If those aren't set, then the command reduces to read -r wcl, and so your entire command line is assigned to the variable wcl, not just the command.
However, read by itself is not going to do the same parsing that the shell already does, if you goal is to write your own shell.
If you are really using bash, you can insert the words you read into positional parameters through an array. (You could also just leave them in the array, but the syntax for referring to positional parameters is simpler.)
# -a: read the successive words into an array
read -r -p "wcl> " -a params
# set the positional parameters to the expansion of the array
set -- "${params[#]}"
wcl=$1 # Or you could do the case on "$1"
This will also set $# to the number of words read, as a side-effect of setting the positional parameters.
As #chepner points outs, the read is problematic: It simply splits the input into whitespace-separated words, without respecting quotes, backslashes, and whatever other shell metacharacters you might want to implement. Doing a full bash-style parse of a command-line in bash itself would be quite a difficult exercise.

Reading variables from config file in Shell

I have question about Linux shell scripts. My question is realy abstract, so may not make sense. The idea is having 1 script and 2 config files.
Script can be like (drinkOutput.sh):
#!/bin/bash
echo -e " $1 \n"
echo -e " $2 \n"
First Config file contain (beer.conf):
drink1="heineken"
drink2="argus"
Second Config file contain (vine.conf):
drink1="chardonnay"
drink2="hibernal"
The key thing is calling the script. It has to be in next format (or with parameter)
./drinkOutput.sh beer.conf
In this case I need to have in $1 heineken and in $2 argus (inside of drinkOutput script). For
./drinkOutput.sh vine.conf
I need to get back into drinkOutput.sh chardonnay and hibernal.
Does anybody know? Thanks for any tips
You can source the config files if they are in the right format (and it seems it is in your example).
drinkOutput()
{
echo "$1"
echo "$2"
}
conf="$1"
source "$conf"
drinkOutput "$drink1" "$drink2"
If is possible if your script calls itself with the proper arguments after having parsed them from the conf file:
if [ $# == 2 ] ; then
# The arguments are correctly set in the sub-shell.
# 2 arguments: do something with them
echo magic happens: $1 $2
elif [ $# == 1 ] ; then
# 1 argument: conf file: parse conf file
arg1=`sed -n -e 's#drink1="\(.*\)"#\1#p' $1`
arg2=`sed -n -e 's#drink2="\(.*\)"#\1#p' $1`
$0 $arg1 $arg2
else
# error
echo "wrong args"
fi
test:
$ drinkOutput.sh beer.conf
magic happens: heineken argus

WHILE loop not looping

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"

Resources