pass string as arguments with spaces to bash function - bash

I'm trying to pass a string to a function. The string contains multiple arguments and some of the arguments may begin with multiple spaces.
#!/bin/bash
test_function() {
echo "arg1 is: '$1'"
echo "arg2 is: '$2'"
echo "arg3 is: '$3'"
}
a_string="one two \" string with spaces in front\""
result=$(test_function $a_string)
echo "$result"
Here is the output actually produced:
arg1 is: 'one'
arg2 is: 'two'
arg3 is: '"'
Here is an example of the output I am trying to achieve:
arg1 is: 'one'
arg2 is: 'two'
arg3 is: ' string with spaces in front'
How can I store arguments containing spaces in a string like this to later be passed to a function?
Although it can be done with an array, I need to first convert the string into the array values.

With an array.
a_string=(one two " string with spaces in front")
result=$(test_function "${a_string[#]}")

bash -c may be what you're looking for (or perhaps even better, eval, as John Kugelman points out below). From the man page,
If the -c option is present, then commands are read from
the first non-option argument command_string. If there
are arguments after the command_string, the first argument
is assigned to $0 and any remaining arguments are
assigned to the positional parameters. The assignment to
$0 sets the name of the shell, which is used in warning
and error messages.
Basically bash -c "foo" is the same (plus a subshell) as foo. In this way we can easily insert our string as arguments.
Here it is in your example:
#!/bin/bash
test_function() {
echo "arg1 is: '$1'"
echo "arg2 is: '$2'"
echo "arg3 is: '$3'"
}
a_string="one two \" string with spaces in front\""
export -f test_function
bash -c "test_function $a_string"
(The export is necessary in this example because it's a defined function, but wouldn't be in other cases).
Output:
arg1 is: 'one'
arg2 is: 'two'
arg3 is: ' string with spaces in front'

Related

Shell command line arguments [duplicate]

This question already has an answer here:
Passing argument containing space in shell script
(1 answer)
Closed 4 years ago.
How to solve below problem.
myscript.sh
echo First argument: $1
echo Second argument: $2
for i in $* do
echo $i
done
now executing the script:
$./myscript.sh "this is" value
First argument: this is
Second argument: value
this
is
value
When I am printing $1 I am getting the value "this is"
but inside loop first argument coming as "this"
I want when the loop execute it will print
this is
value
This is due to the usage of $* instead of "$#" to get all of the arguments. From ShellCheck:
$*, unquoted, is subject to word splitting and globbing.
Let's say you have three arguments: baz, foo bar and *
"$#" will expand into exactly that: baz, foo bar and *
$* will expand into multiple other arguments: baz, foo, bar, file.txt
and otherfile.jpg
For the desired behavior you can do this:
echo "First argument: $1"
echo "Second argument: $2"
for i in "$#"
do echo "$i"
done

Why the count of "arg1 $#" as bash function arguments is less?

I wrote a bash script countArgs.sh as below:
#!/bin/bash
function count
{
echo $#
}
count "arg1 $#"
I expect the output of the script should be the number of its input plus 1, while the result is like this:
./countArgs.sh a b c
3
You are getting count 3 because you have enclosed argument list in double quotes here:
count "arg1 $#"
And due to this count function is getting these 3 arguments only (in each separate line):
arg1 a
b
c
You can get same output if you place this printf line in your count function:
count() {
printf "%s\n" "$#"
echo $#
}
Note how first positional argument is arg1 a instead of arg1.
If you remove quotes around arg1 $# and call it as:
count arg1 "$#"
then you will get 4 as output.
Two point:
first, you are echoing inside a function, it means that $# will give you the number or arguments or your function, not of your script.
Second, you are calling the function with parameter $# between quotes ", but $# already does it by default, so after variable expansion in fact it will be like this:
count "arg1 a" "b" "c"
Run you script with bash -x and you will see how it is working:
#!/bin/bash -x
function count
{
echo $#
}
count "arg1 $#"

How to use bash substitution to append a newline at the end of each element of a list

I am looking for a bash one liner that appends a newline after each element of a list. If I call the script as:
./script arg1 arg2 arg3
I want the output to be
arg1
arg2
arg3
I tried different variations of the following. The newline does not get added. Any ordinary char gets added.
# pretty much works except for an extra space
list=${#/%/x}
echo "$list"
# appends 'n'
list=${#/%/\n}
echo "$list"
# appends nothing
list=${#/%/$'\n'}
echo "$list"
# appends nothing, \x078 would append 'x'
list=${#/%/$'\x0D'}
echo "$list"
# appends nothing
CR=$'\n'
list=${#/%/$CR}
echo "$list"
# same issues with arrays
tmp=($#)
list=${tmp/%/\n}
echo "$list"
What fix or alternative do you suggest? I obviously could write a loop or call tr but that's precisely what I thought I could avoid with a bash substitution.
You can use this function with "$#":
f() { printf "%s\n" "$#"; }
f arg1 arg2 arg3
arg1
arg2
arg3
As per man bash
# Expands to the positional parameters, starting from one. When the expansion occurs
within double quotes, each parameter expands to a separate word. That is, "$#" is
equivalent to "$1" "$2" ...
printf would have been my answer as well. Another technique is to use IFS:
$ IFS=$'\n'
$ list="$*"
$ echo "$list"
arg1
arg2
arg3
Notes:
uses ANSI-C quoting for the newline sequence
"$*" (with the quotes, crucial) joins the positional params using the first char of $IFS
quote the shell variable for the echo command to preserve the inner newlines.
That redefines the IFS value for the current shell. You can save the old value first and restore it after:
oldIFS=$IFS; IFS=$'\n'; list="$*"; IFS=$oldIFS
or you can use a subshell so the modification is discarded for you:
$ list=$( IFS=$'\n'; echo "$*" )

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.

What is the difference between "$#" and "$*" in Bash? [duplicate]

This question already has answers here:
What is the difference between $# and $* in shell scripts?
(3 answers)
Closed 8 years ago.
It seems to me that they both store all the command-line arguments.
So is there a difference between the two?
The difference is subtle; "$*" creates one argument separated by the $IFS variable, while "$#" will expand into separate arguments. As an example, consider:
for i in "$#"; do echo "# '$i'"; done
for i in "$*"; do echo "* '$i'"; done
When run with multiple arguments:
./testvar foo bar baz 'long arg'
# 'foo'
# 'bar'
# 'baz'
# 'long arg'
* 'foo bar baz long arg'
For more details:
http://www.gnu.org/software/bash/manual/bashref.html#Special-Parameters
$*
Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable. That is, "$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable. If IFS is unset, the parameters are separated by spaces. If IFS is null, the parameters are joined without intervening separators.
$#
Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is, "$#" is equivalent to "$1" "$2" .... If the double-quoted expansion occurs within a word, the expansion of the first parameter is joined with the beginning part of the original word, and the expansion of the last parameter is joined with the last part of the original word. When there are no positional parameters, "$#" and $# expand to nothing (i.e., they are removed).
A key difference from my POV is that "$#" preserves the original number
of arguments. It's the only form that does.
For example, if file my_script contains:
#!/bin/bash
main()
{
echo 'MAIN sees ' $# ' args'
}
main $*
main $#
main "$*"
main "$#"
### end ###
and I run it like this:
my_script 'a b c' d e
I will get this output:
MAIN sees 5 args
MAIN sees 5 args
MAIN sees 1 args
MAIN sees 3 args

Resources