Bash function arguments not passed as expected - bash

I have an unexpected behaviour in my script:
#!/bin/bash
checkFolders "first folder"
checkFolders() {
checkEmptyVar $1 "folder to be copied"
}
checkEmptyVar() {
echo "0: $0 1: $1 2: $2"
[[ $(isNotEmpty $1) = false ]] && (echo "Specify a path for $2") && exit
echo "post exit"
}
The function checkEmptyVar echoes the following:
0: ./lcp.sh 1: folder to be copied 2:
I expected to have passed "folder-to-be-copied" as $1of checkEmptyVar, what is happening?

You have numerous problems:
$0 is not the first argument to a function; $1 is. $0 is the name of the current script.
You must quote parameter expansions to prevent them from being split into multiple words on embedded whitespace.
Functions must be defined before they are used.
The correct script is
#!/bin/bash
checkFolders() {
checkEmptyVar "$1" "folder to be copied"
}
checkEmptyVar() {
[[ $(isNotEmpty "$1") = false ]] && echo "Specify a path for $2" && exit
echo "post exit"
}
checkFolders "first folder"
Further, it would be better to have isNotEmpty return a non-zero value instead of outputting the string false, so that you could write
checkEmptyVar () {
if isNotEmpty "$1"; then
echo "Specify a path for $2" >&2 # Use standard error, not standard output
exit 1
fi
echo "post exit"
}
(I suspect you could replace isNotEmpty with [[ -n $1 ]] anyway.)

This script isn't doing what you think it is. Your function definitions are happening too late.
When you call checkFolders on the first line you are calling a version of that function from the pre-existing environment and not the one defined later in that script.
If you run command -V checkFolders from the shell you are running this script from I expect you'll get output somewhat like:
checkFolders is a function
checkFolder ()
{
checkEmptyVar "folder to be copied"
}
though anything is possible there.
You should also always quote variables when you use them to prevent the shell from word-splitting their contents.
Without doing that when you call checkFolders "first folder" that ends up calling checkFolders first folder "folder to be copied" which isn't what you want at all.
Invert the order of your functions and calls and fix your variable quoting and you should see what you expect.

Related

How to create dynamic alias (Conditional command) in bash?

I want to create shortcut for 'code file' (VS Code) if there's a second argument, or 'clear' if there's none in a single command line, but i don't know how the bash syntax works.
By looking at the mkcd (mkdir & cd) shortcut that i created:
function mkcd {
if [ ! -n "$1" ]; then
echo "Enter a directory name"
elif [ -d $1 ]; then
echo "\`$1' already exists"
else
mkdir $1 && cd $1
fi
}
I tried to do the same, but the error shows 'syntax error near unexpected token `else''
function c {
if $1 then
code $1
else
clear
fi
}
Your syntax error is that if $1 then is missing a semi-colon (e.g. if $1; then) but read on... There are no "shortcut"s in UNIX. There are scripts, functions, and aliases. It looks like you want to create a function named c to call some command named code when an argument is given, some other command named clear otherwise. That'd be:
c() {
if (( $# > 0 )); then
code "$*"
else
clear
fi
}

Substituting variable inside a string stored in array - bash

My strings are stored inside array, a sample string contains a 'variable'.
While iterating over this array, I want to substitute the 'variable' with a 'value'.
This fails for me. I have tried my lot in various ways & googled but could not figure it out
# Array of strings (each string is a command)
clean_aa_commands=(
"sourceanalyzer -b ${FortifyBuildId} -clean"
"cd ${unifiedbuilddir}/AA/AAUI"
"mvn clean"
)
# Functions
function check {
if [ "$?" -ne 0 ]; then
echo "Operation [$1] Unsuccessful!"
else
echo "Operation [$1] Success!"
fi
}
function runcmds {
echo "using $FortifyBuildId *************************"
cmdArr=("${!1}")
for cmd in "${cmdArr[#]}"
do
echo "-->Running [$cmd]"
eval "$cmd"
check "$cmd"
echo ""
echo ""
done
}
# main
FortifyBuildId="$1"
echo "FortifyBuildId is $FortifyBuildId"
unifiedbuilddir=`pwd`
runcmds clean_aa_commands["#"]
How about this
cmds='cmds=( /test/$mydir /test/$test )'
mydir=$(pwd)
test="me"
eval ${cmds}
echo ${cmds[#]}

arithmetic syntax error in ksh if condition

Part of my KornShell (ksh) script is as follows .
let "value=`awk NR==14 ${TEMP_DIR}/IR4723/count_part_UNVM.txt`"
let "var= value"
if [ $var -gt 0 ]
then
load_data ${count_part[$i]}
fi
first line in above part of script throw following error:
arithmetic syntax error LOADTODB/ShellsAndSQLs/IR4723/load_gen_tmp_tab.ksh[84]: test: 8
above script reading line number 14 from file count_part_UNVM.txt value 8.
I am stuck in this issue. Please help me to solve issue.
Is that a typo, or are you missing the '$' in front of value, i.e. (your 2nd line)
let "var=$value"
which I think should really be
let var="$value"
You can eliminate a lot of problems while you're building your script by turning on the shell debugging feature, i.e.
set -vx
let "var= value"
set +vx
Or put set -vx near the top of the script and see how all of the script is processed.
EDIT3 OR put -vx after #!/bin/bash on the first line, i.e. #!/bin/bash -vx.
EDIT2 I missed your first comment, yes, line1 looks problematic too. Try this
let value="$(awk 'NR==14' ${TEMP_DIR}/IR4723/count_part_UNVM.txt)"
Note that using backticks for cmd-substitution was declared as deprecated in the 'New Kornshell Language' ~ 1995. $( ) for cmd-substition is easily nestable and your best bet.
ALSO, note that an awk script (on a cmdline) should be presented as single argument, by surrounding it with single or dbl-quotes. (But I'm not absolutely certain you need quotes given that your 'program' has no spaces in it.) If the revision doesn't work, add print --"value=XX${value}XX after the assignment and edit your question above to show the output.
EDIT Per your comment/question, there are a few shell script debuggers around, but I never use them. Search for Rosenberg ksh book debugger, if you really want to try it.
The set -vx should really be called an "execution trace". It shows you the line or block of code before it is executed, then with any variables expanded to their values. That it the typical way people debug ksh scripts. You can also add statments like print -- "var=XX${var}XX" to see individual values of variables, but that can mess up your output and may required you to turn of those statments, forcing you to edit your script again.
I hope this helps.
Why are you using let in a ksh? Here is a suggestion for your code.
example.ksh
#!/bin/ksh
#Initialize Varables
TEMP_DIR=tempDir/IR4723
var=""
initialize(){
echo "Entering initialize"
echo "Exiting initialize"
}
function1(){
echo "Entering function1"
echo ${PWD}
file=${PWD}/${TEMP_DIR}/count_part_UNVM.txt
readLine "${file}"
echo "var: "${var}
if [[ ${var} -gt 0 ]]; then
load_data ${count_part[$i]}
fi
echo "Exiting function1"
}
createDir(){
echo "Entering createDir"
mkdir -p ${1}
echo "Exiting createDir"
}
createFileWithRandomCount(){
echo "Entering createFileWithRandomCount"
orgDirectory=${PWD}
cd ${1}
> ${2}
print ${RANDOM} >> ${2}
cd ${orgDirectory}
echo "Exiting createFileWithRandomCount"
}
readLine(){
echo "Entering readLine"
file=${1}
while read line
do
# display line or do somthing on $line
var="$line"
done <"$file"
echo "Exiting readLine"
}
load_data(){
echo "Entering load_data"
echo "Exiting load_data"
}
#-----------
#---Main----
#-----------
echo "Starting: "${0}" with Input Parameters: {1: "${1}" {2: "${2}" {3: "${3}
initialize #function call#
createDir ${TEMP_DIR} #function call#
createFileWithRandomCount ${TEMP_DIR} count_part_UNVM.txt #function call#
function1 #function call#
Output:
$ ksh -i example.ksh
Starting: example.ksh with Input Parameters: {1: {2: {3:
Entering initialize
Exiting initialize
Entering createDir
Exiting createDir
Entering createFileWithRandomCount
Exiting createFileWithRandomCount
Entering function1
/tmp
Entering readLine
Exiting readLine
var: 11984
Entering load_data
Exiting load_data
Exiting function1

In bash, is there an equivalent of die "error msg"

In perl, you can exit with an error msg with die "some msg". Is there an equivalent single command in bash? Right now, I'm achieving this using commands: echo "some msg" && exit 1
You can roll your own easily enough:
die() { echo "$*" 1>&2 ; exit 1; }
...
die "Kaboom"
Here's what I'm using. It's too small to put in a library so I must have typed it hundreds of times ...
warn () {
echo "$0:" "$#" >&2
}
die () {
rc=$1
shift
warn "$#"
exit $rc
}
Usage: die 127 "Syntax error"
This is a very close function to perl's "die" (but with function name):
function die
{
local message=$1
[ -z "$message" ] && message="Died"
echo "$message at ${BASH_SOURCE[1]}:${FUNCNAME[1]} line ${BASH_LINENO[0]}." >&2
exit 1
}
And bash way of dying if built-in function is failed (with function name)
function die
{
local message=$1
[ -z "$message" ] && message="Died"
echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}: ${FUNCNAME[1]}: $message." >&2
exit 1
}
So, Bash is keeping all needed info in several environment variables:
LINENO - current executed line number
FUNCNAME - call stack of functions, first element (index 0) is current function, second (index 1) is function that called current function
BASH_LINENO - call stack of line numbers, where corresponding FUNCNAME was called
BASH_SOURCE - array of source file, where corresponfing FUNCNAME is stored
Yep, that's pretty much how you do it.
You might use a semicolon or newline instead of &&, since you want to exit whether or not echo succeeds (though I'm not sure what would make it fail).
Programming in a shell means using lots of little commands (some built-in commands, some tiny programs) that do one thing well and connecting them with file redirection, exit code logic and other glue.
It may seem weird if you're used to languages where everything is done using functions or methods, but you get used to it.
# echo pass params and print them to a log file
wlog(){
# check terminal if exists echo
test -t 1 && echo "`date +%Y.%m.%d-%H:%M:%S` [$$] $*"
# check LogFile and
test -z $LogFile || {
echo "`date +%Y.%m.%d-%H:%M:%S` [$$] $*" >> $LogFile
} #eof test
}
# eof function wlog
# exit with passed status and message
Exit(){
ExitStatus=0
case $1 in
[0-9]) ExitStatus="$1"; shift 1;;
esac
Msg="$*"
test "$ExitStatus" = "0" || Msg=" ERROR: $Msg : $#"
wlog " $Msg"
exit $ExitStatus
}
#eof function Exit

Can I pass an arbitrary block of commands to a bash function?

I am working on a bash script where I need to conditionally execute some things if a particular file exists. This is happening multiple times, so I abstracted the following function:
function conditional-do {
if [ -f $1 ]
then
echo "Doing stuff"
$2
else
echo "File doesn't exist!"
end
}
Now, when I want to execute this, I do something like:
function exec-stuff {
echo "do some command"
echo "do another command"
}
conditional-do /path/to/file exec-stuff
The problem is, I am bothered that I am defining 2 things: the function of a group of commands to execute, and then invoking my first function.
I would like to pass this block of commands (often 2 or more) directly to "conditional-do" in a clean manner, but I have no idea how this is doable (or if it is even possible)... does anyone have any ideas?
Note, I need it to be a readable solution... otherwise I would rather stick with what I have.
This should be readable to most C programmers:
function file_exists {
if ( [ -e $1 ] ) then
echo "Doing stuff"
else
echo "File $1 doesn't exist"
false
fi
}
file_exists filename && (
echo "Do your stuff..."
)
or the one-liner
file_exists filename && echo "Do your stuff..."
Now, if you really want the code to be run from the function, this is how you can do that:
function file_exists {
if ( [ -e $1 ] ) then
echo "Doing stuff"
shift
$*
else
echo "File $1 doesn't exist"
false
fi
}
file_exists filename echo "Do your stuff..."
I don't like that solution though, because you will eventually end up doing escaping of the command string.
EDIT: Changed "eval $*" to $ *. Eval is not required, actually. As is common with bash scripts, it was written when I had had a couple of beers ;-)
One (possibly-hack) solution is to store the separate functions as separate scripts altogether.
The cannonical answer:
[ -f $filename ] && echo "it has worked!"
or you can wrap it up if you really want to:
function file-exists {
[ "$1" ] && [ -f $1 ]
}
file-exists $filename && echo "It has worked"

Resources