How to identify/pass bash arguments properly [duplicate] - bash

This question already has answers here:
Is there a way to avoid positional arguments in bash?
(8 answers)
Closed 3 years ago.
This question is more oriented on what is the best practice to treat arguments in bash functions. Let's take a look to the following code:
#!/bin/bash
do_something () {
echo "$1"
}
do_something_1 () {
echo "$1"
}
do_something_2 () {
echo "$1"
}
do_something_3 () {
echo "$1"
}
echo "$1"
do_something
do_something "hi"
do_something_2 "hello"
do_something_3 "bye"
And let's imagine I am calling the script:
./myscript.sh param1
This will output:
param1 #First parameter passed to the string
#Nothing, as I am passing nothing to do_something
hi #first parameter of do_something
hello #first parameter of do_something_2
bye #first parameter of do_something_3
but if I take a look at the functions, all of those are called "$1". Now, I understand this, but this doesn't seems readable. What if the code is bigger? I will need to go to the caller of the function to see what argument was passed (and ignore the parameter that was passed to the script), and it will become more and more difficult to know/maintain what is inside the parameters passed.

For larger functions I do:
function myfunc() # source dest [options]
{
local source="$1"
local dest="$2"
local options="$3"
# Now i have named local variables
....
}

You can try :
do_something "$1"
Or you can use a langauage programmation like php if you'll have a big cide

Related

How to pass an argument passed to a bashrc function? [duplicate]

I am trying to search how to pass parameters in a Bash function, but what comes up is always how to pass parameter from the command line.
I would like to pass parameters within my script. I tried:
myBackupFunction("..", "...", "xx")
function myBackupFunction($directory, $options, $rootPassword) {
...
}
But the syntax is not correct. How can I pass a parameter to my function?
There are two typical ways of declaring a function. I prefer the second approach.
function function_name {
command...
}
or
function_name () {
command...
}
To call a function with arguments:
function_name "$arg1" "$arg2"
The function refers to passed arguments by their position (not by name), that is $1, $2, and so forth. $0 is the name of the script itself.
Example:
function_name () {
echo "Parameter #1 is $1"
}
Also, you need to call your function after it is declared.
#!/usr/bin/env sh
foo 1 # this will fail because foo has not been declared yet.
foo() {
echo "Parameter #1 is $1"
}
foo 2 # this will work.
Output:
./myScript.sh: line 2: foo: command not found
Parameter #1 is 2
Reference: Advanced Bash-Scripting Guide.
Knowledge of high level programming languages (C/C++, Java, PHP, Python, Perl, etc.) would suggest to the layman that Bourne Again Shell (Bash) functions should work like they do in those other languages.
Instead, Bash functions work like shell commands and expect arguments to be passed to them in the same way one might pass an option to a shell command (e.g. ls -l). In effect, function arguments in Bash are treated as positional parameters ($1, $2..$9, ${10}, ${11}, and so on). This is no surprise considering how getopts works. Do not use parentheses to call a function in Bash.
(Note: I happen to be working on OpenSolaris at the moment.)
# Bash style declaration for all you PHP/JavaScript junkies. :-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
function backupWebRoot ()
{
tar -cvf - "$1" | zip -n .jpg:.gif:.png "$2" - 2>> $errorlog &&
echo -e "\nTarball created!\n"
}
# sh style declaration for the purist in you. ;-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
backupWebRoot ()
{
tar -cvf - "$1" | zip -n .jpg:.gif:.png "$2" - 2>> $errorlog &&
echo -e "\nTarball created!\n"
}
# In the actual shell script
# $0 $1 $2
backupWebRoot ~/public/www/ webSite.tar.zip
Want to use names for variables? Just do something this.
local filename=$1 # The keyword declare can be used, but local is semantically more specific.
Be careful, though. If an argument to a function has a space in it, you may want to do this instead! Otherwise, $1 might not be what you think it is.
local filename="$1" # Just to be on the safe side. Although, if $1 was an integer, then what? Is that even possible? Humm.
Want to pass an array to a function by value?
callingSomeFunction "${someArray[#]}" # Expands to all array elements.
Inside the function, handle the arguments like this.
function callingSomeFunction ()
{
for value in "$#" # You want to use "$#" here, not "$*" !!!!!
do
:
done
}
Need to pass a value and an array, but still use "$#" inside the function?
function linearSearch ()
{
local myVar="$1"
shift 1 # Removes $1 from the parameter list
for value in "$#" # Represents the remaining parameters.
do
if [[ $value == $myVar ]]
then
echo -e "Found it!\t... after a while."
return 0
fi
done
return 1
}
linearSearch $someStringValue "${someArray[#]}"
In Bash 4.3 and above, you can pass an array to a function by reference by defining the parameter of a function with the -n option.
function callingSomeFunction ()
{
local -n someArray=$1 # also ${1:?} to make the parameter mandatory.
for value in "${someArray[#]}" # Nice!
do
:
done
}
callingSomeFunction myArray # No $ in front of the argument. You pass by name, not expansion / value.
If you prefer named parameters, it's possible (with a few tricks) to actually pass named parameters to functions (also makes it possible to pass arrays and references).
The method I developed allows you to define named parameters passed to a function like this:
function example { args : string firstName , string lastName , integer age } {
echo "My name is ${firstName} ${lastName} and I am ${age} years old."
}
You can also annotate arguments as #required or #readonly, create ...rest arguments, create arrays from sequential arguments (using e.g. string[4]) and optionally list the arguments in multiple lines:
function example {
args
: #required string firstName
: string lastName
: integer age
: string[] ...favoriteHobbies
echo "My name is ${firstName} ${lastName} and I am ${age} years old."
echo "My favorite hobbies include: ${favoriteHobbies[*]}"
}
In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays (and references to variables - this feature works only in Bash 4.3 though)! Plus, the mapped variables are all in the local scope, just as $1 (and others).
The code that makes this work is pretty light and works both in Bash 3 and Bash 4 (these are the only versions I've tested it with). If you're interested in more tricks like this that make developing with bash much nicer and easier, you can take a look at my Bash Infinity Framework, the code below is available as one of its functionalities.
shopt -s expand_aliases
function assignTrap {
local evalString
local -i paramIndex=${__paramIndex-0}
local initialCommand="${1-}"
if [[ "$initialCommand" != ":" ]]
then
echo "trap - DEBUG; eval \"${__previousTrap}\"; unset __previousTrap; unset __paramIndex;"
return
fi
while [[ "${1-}" == "," || "${1-}" == "${initialCommand}" ]] || [[ "${##}" -gt 0 && "$paramIndex" -eq 0 ]]
do
shift # First colon ":" or next parameter's comma ","
paramIndex+=1
local -a decorators=()
while [[ "${1-}" == "#"* ]]
do
decorators+=( "$1" )
shift
done
local declaration=
local wrapLeft='"'
local wrapRight='"'
local nextType="$1"
local length=1
case ${nextType} in
string | boolean) declaration="local " ;;
integer) declaration="local -i" ;;
reference) declaration="local -n" ;;
arrayDeclaration) declaration="local -a"; wrapLeft= ; wrapRight= ;;
assocDeclaration) declaration="local -A"; wrapLeft= ; wrapRight= ;;
"string["*"]") declaration="local -a"; length="${nextType//[a-z\[\]]}" ;;
"integer["*"]") declaration="local -ai"; length="${nextType//[a-z\[\]]}" ;;
esac
if [[ "${declaration}" != "" ]]
then
shift
local nextName="$1"
for decorator in "${decorators[#]}"
do
case ${decorator} in
#readonly) declaration+="r" ;;
#required) evalString+="[[ ! -z \$${paramIndex} ]] || echo \"Parameter '$nextName' ($nextType) is marked as required by '${FUNCNAME[1]}' function.\"; " >&2 ;;
#global) declaration+="g" ;;
esac
done
local paramRange="$paramIndex"
if [[ -z "$length" ]]
then
# ...rest
paramRange="{#:$paramIndex}"
# trim leading ...
nextName="${nextName//\./}"
if [[ "${##}" -gt 1 ]]
then
echo "Unexpected arguments after a rest array ($nextName) in '${FUNCNAME[1]}' function." >&2
fi
elif [[ "$length" -gt 1 ]]
then
paramRange="{#:$paramIndex:$length}"
paramIndex+=$((length - 1))
fi
evalString+="${declaration} ${nextName}=${wrapLeft}\$${paramRange}${wrapRight}; "
# Continue to the next parameter:
shift
fi
done
echo "${evalString} local -i __paramIndex=${paramIndex};"
}
alias args='local __previousTrap=$(trap -p DEBUG); trap "eval \"\$(assignTrap \$BASH_COMMAND)\";" DEBUG;'
Drop the parentheses and commas:
myBackupFunction ".." "..." "xx"
And the function should look like this:
function myBackupFunction() {
# Here $1 is the first parameter, $2 the second, etc.
}
A simple example that will clear both during executing script or inside script while calling a function.
#!/bin/bash
echo "parameterized function example"
function print_param_value(){
value1="${1}" # $1 represent first argument
value2="${2}" # $2 represent second argument
echo "param 1 is ${value1}" # As string
echo "param 2 is ${value2}"
sum=$(($value1+$value2)) # Process them as number
echo "The sum of two value is ${sum}"
}
print_param_value "6" "4" # Space-separated value
# You can also pass parameters during executing the script
print_param_value "$1" "$2" # Parameter $1 and $2 during execution
# Suppose our script name is "param_example".
# Call it like this:
#
# ./param_example 5 5
#
# Now the parameters will be $1=5 and $2=5
It takes two numbers from the user, feeds them to the function called add (in the very last line of the code), and add will sum them up and print them.
#!/bin/bash
read -p "Enter the first value: " x
read -p "Enter the second value: " y
add(){
arg1=$1 # arg1 gets to be the first assigned argument (note there are no spaces)
arg2=$2 # arg2 gets to be the second assigned argument (note there are no spaces)
echo $(($arg1 + $arg2))
}
add x y # Feeding the arguments
Another way to pass named parameters to Bash... is passing by reference. This is supported as of Bash 4.0
#!/bin/bash
function myBackupFunction(){ # directory options destination filename
local directory="$1" options="$2" destination="$3" filename="$4";
echo "tar cz ${!options} ${!directory} | ssh root#backupserver \"cat > /mnt/${!destination}/${!filename}.tgz\"";
}
declare -A backup=([directory]=".." [options]="..." [destination]="backups" [filename]="backup" );
myBackupFunction backup[directory] backup[options] backup[destination] backup[filename];
An alternative syntax for Bash 4.3 is using a nameref.
Although the nameref is a lot more convenient in that it seamlessly dereferences, some older supported distros still ship an older version, so I won't recommend it quite yet.

how to pass parameter to a function when it's called inside another function in shell

I have shell script with 2 functions like below:
lines(){
while IFS="" read -l
do
line=$(wc -l < "something.txt")
if [ "$line" = "$1" ] ; then
do something...
echo "lines are: "$l""
else
#calling files function here
files
do something...
fi
done<something.txt
}
files(){
do something....
echo "something...\n""$(lines "$1")"
}
####Main
case "$1" in
lines)
shift
lines "$1"
;;
*)
esac
I am trying to run the script like this on an ubuntu machine:
sh files.sh line 3
I have some if operations where
In files I am trying to call lines function. When it's called and goes back to perform the actions in lines(), the argument I am trying to pass from the command line 3 i.e., "$1" is being passed as null (empty)
Can someone help me how I can have lines function read the parameter I am passing from the command line
Thanks
The easy thing to do is to just create a global array variable that preserves your original command-line arguments:
#!/usr/bin/env bash
[ "$BASH_VERSION" ] || { echo "ERROR: This script requires bash" >&2; exit 1; }
args=( "$0" "$#" )
func1() { func2 "local-arg1" "local-arg2"; }
func2() { echo "Function argument 1 is $1; original argument 2 is ${args[2]}"; }
func1
...will, if called as ./scriptname global-arg1 global-arg2, emit as output:
Function argument 1 is local-arg1; original argument 2 is global-arg2
Positional parameters are local to each function. So files can't access the $1 variable of lines by itself, you need to pass it explicitly:
files "$1"

Returning value from a function in shell script [duplicate]

This question already has answers here:
How to return a string value from a Bash function
(20 answers)
Closed 5 years ago.
I would like to pass an argument to a function and return a calculated value from it to be stored for further process. Below is the sample code.
#!/bin/bash
test()
{
echo $1
c=$(expr $1 + "10000")
return $c
}
var=$(test 10)
echo $var
I would like to get the value of c stored in var. Can anyone please help in this case.
The "return value" of a function as you used it is stdout.
"return" will set exit status ($?), which you probably have no use for.
"test" is probably a bad choice of name, since it's taken (qv. man test).
So:
$ Test() { expr $1 + 10000; }
$ var=$(Test 10)
$ echo $var
10010
if all you wish to do is add 10000 to your input, then a function is overkill. for this, wouldnt this work?
your_arg=10
var=$(( ${your_arg}+10000 ))
echo $var
There are some issues in your code.
#!/bin/bash
It works but it is no good idea to define a function called test, because test is a standard Unix program.
test()
{
Write debug output to standard error (&2) instead of standard output (&1). Standard output is used to return data.
echo "$1" >&2
Declare variables in functions with local to avoid side effects.
local c=$(expr "$1" + "10000")
Use echo instead of return to return strings. return can return only integers.
echo "$c"
}
var=$(test 10)
If unsure always quote arguments.
echo "$var" >&2

Change global positional parameters inside a Bash function

Is there a way to set the positional parameters of a bash script from within a function?
In the global scope one can use set -- <arguments> to change the positional arguments, but it doesn't work inside a function because it changes the function's positional parameters.
A quick illustration:
# file name: script.sh
# called as: ./script.sh -opt1 -opt2 arg1 arg2
function change_args() {
set -- "a" "c" # this doesn't change the global args
}
echo "original args: $#" # original args: -opt1 -opt2 arg1 arg2
change_args
echo "changed args: $#" # changed args: -opt1 -opt2 arg1 arg2
# desired outcome: changed args: a c
As stated before, the answer is no, but if someone need this there's the option of setting an external array (_ARRAY), modifying it from within the function and then using set -- ${_ARRAY[#]} after the fact. For example:
#!/bin/bash
_ARGS=()
shift_globally () {
shift
_ARGS=$#
}
echo "before: " "$#"
shift_globally "$#"
set -- "${_ARGS[#]}"
echo "after: " "$#"
If you test it:
./test.sh a b c d
> before: a b c d
> after: b c d
It's not technically what you're asking for but it's a workaround that might help someone who needs a similar behaviour.
Not really. A function actually has its own scope. A parameter assigned in a function is global by default, but if you explicitly declare it it has local scope:
foo() {
x=3 # global
declare y # local
...
}
The positional parameters are always local to the function, so anything you do to manipulate them will only take effect within the function scope.
Technically, you can always use recursion to solve this issue. If you can confidently call the original script again, you can reorder the arguments that way. E.g.
declare -i depth
outer() {
if (( depth++ < 1 )); then
outer "${#:4:$#}" "${#:1:3}"
return
fi
main "$#"
}
main() {
printf '%s\n' "$#"
}
outer "$#"
This seems like an error prone and confusing solution to a problem I don't understand, but it essentially works.
I was searching for this myself and came up with the below, i.e. return a space separated string of the array (sorry, positional parameters) from the function by printing it to stdout and capture it with command substitution, then parse the string word for word and append it back into the positional arguments. Clear as mud!
Obviously won't work for args with spaces in them below but that's of course possible to workaround too.
#!/bin/sh
fn() {
set -- d e f
res=""
for i; do
res="${res:+${res} }${i}"
done
printf "%s\n" "$res"
}
set -- a b c
rv=$(fn)
set --
for word in $rv; do
set -- "$#" "$word"
done
for i; do
echo positional parms "$i"
done

How to access command line arguments of the caller inside a function?

I'm attempting to write a function in bash that will access the scripts command line arguments, but they are replaced with the positional arguments to the function. Is there any way for the function to access the command line arguments if they aren't passed in explicitly?
# Demo function
function stuff {
echo $0 $*
}
# Echo's the name of the script, but no command line arguments
stuff
# Echo's everything I want, but trying to avoid
stuff $*
If you want to have your arguments C style (array of arguments + number of arguments) you can use $# and $#.
$# gives you the number of arguments.
$# gives you all arguments. You can turn this into an array by args=("$#").
So for example:
args=("$#")
echo $# arguments passed
echo ${args[0]} ${args[1]} ${args[2]}
Note that here ${args[0]} actually is the 1st argument and not the name of your script.
My reading of the Bash Reference Manual says this stuff is captured in BASH_ARGV,
although it talks about "the stack" a lot.
#!/bin/bash
shopt -s extdebug
function argv {
for a in ${BASH_ARGV[*]} ; do
echo -n "$a "
done
echo
}
function f {
echo f $1 $2 $3
echo -n f ; argv
}
function g {
echo g $1 $2 $3
echo -n g; argv
f
}
f boo bar baz
g goo gar gaz
Save in f.sh
$ ./f.sh arg0 arg1 arg2
f boo bar baz
fbaz bar boo arg2 arg1 arg0
g goo gar gaz
ggaz gar goo arg2 arg1 arg0
f
fgaz gar goo arg2 arg1 arg0
#!/usr/bin/env bash
echo name of script is $0
echo first argument is $1
echo second argument is $2
echo seventeenth argument is $17
echo number of arguments is $#
Edit: please see my comment on question
Ravi's comment is essentially the answer. Functions take their own arguments. If you want them to be the same as the command-line arguments, you must pass them in. Otherwise, you're clearly calling a function without arguments.
That said, you could if you like store the command-line arguments in a global array to use within other functions:
my_function() {
echo "stored arguments:"
for arg in "${commandline_args[#]}"; do
echo " $arg"
done
}
commandline_args=("$#")
my_function
You have to access the command-line arguments through the commandline_args variable, not $#, $1, $2, etc., but they're available. I'm unaware of any way to assign directly to the argument array, but if someone knows one, please enlighten me!
Also, note the way I've used and quoted $# - this is how you ensure special characters (whitespace) don't get mucked up.
# Save the script arguments
SCRIPT_NAME=$0
ARG_1=$1
ARGS_ALL=$*
function stuff {
# use script args via the variables you saved
# or the function args via $
echo $0 $*
}
# Call the function with arguments
stuff 1 2 3 4
One can do it like this as well
#!/bin/bash
# script_name function_test.sh
function argument(){
for i in $#;do
echo $i
done;
}
argument $#
Now call your script like
./function_test.sh argument1 argument2
This is #mcarifio response with several comments incorporated:
#!/bin/bash
shopt -s extdebug
function stuff() {
local argIndex="${#BASH_ARGV[#]}"
while [[ argIndex -gt 0 ]] ; do
argIndex=$((argIndex - 1))
echo -n "${BASH_ARGV[$argIndex]} "
done
echo
}
stuff
I want to highlight:
The shopt -s extdebug is important. Without this the BASH_ARGV array will be empty unless you use it in top level part of the script (it means outside of the stuff function). Details here: Why does the variable BASH_ARGV have a different value in a function, depending on whether it is used before calling the function
BASH_ARGV is a stack so arguments are stored there in backward order. That's the reason why I decrement the index inside loop so we get arguments in the right order.
Double quotes around the ${BASH_ARGV[#]} and the # as an index instead of * are needed so arguments with spaces are handled properly. Details here: bash arrays - what is difference between ${#array_name[*]} and ${#array_name[#]}
You can use the shift keyword (operator?) to iterate through them.
Example:
#!/bin/bash
function print()
{
while [ $# -gt 0 ]
do
echo "$1"
shift 1
done
}
print "$#"
I do it like this:
#! /bin/bash
ORIGARGS="$#"
function init(){
ORIGOPT= "- $ORIGARGS -" # tacs are for sed -E
echo "$ORIGOPT"
}
The simplest and likely the best way to get arguments passed from the command line to a particular function is to include the arguments directly in the function call.
# first you define your function
function func_ImportantPrints() {
printf '%s\n' "$1"
printf '%s\n' "$2"
printf '%s\n' "$3"
}
# then when you make your function call you do this:
func_ImportantPrints "$#"
This is useful no matter if you are sending the arguments to main or some function like func_parseArguments (a function containing a case statement as seen in previous examples) or any function in the script.

Resources