Shell command line arguments [duplicate] - shell

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

Related

Reverse Command Line Parameters in a bash script

I have to write a simple bash script for my programming class. The idea is to use a for loop with $* (names of Files as Command Line Parameters). The task is to reverse and print out the Command Line parameters while still using the for inFile in $*; do loop.
I have no idea how to do this.
#!/bin/bash
for inFile in $*;do
echo $inFile
done
I know this doesn't work it just prints out the command line parameters in order.
The idea to loop over $* to reverse command line arguments is broken,
when any command line argument contains a white space. For example when the command line arguments are foo and "bar baz", the output of the script in the question will be:
foo
bar
baz
When the correct output should be:
foo
bar baz
The exact wording of the task is important.
For example, if the task is to print the arguments in reverse, and it doesn't mention $*, then you can use a counting loop in reverse, and ${!i} to expand to the value of the numbered positional parameters:
# nice clean solution
for ((i = $#; i > 0; i--)); do
echo "${!i}"
done
Another example, if the task insists that you must use $* and accepts that the command line arguments will only have supported characters, then you could collect the parameters into an array, and then print the content of the array in reverse, again with a counting loop:
args=()
# not recommended, unsafe due to shell expansion of $*
for arg in $*; do
args+=("$arg")
done
for ((i = ${#args[#]} - 1; i >= 0; i--)); do
echo "${args[i]}"
done
If you are not allowed to use arrays, then you can prepend values to a string, and then iterate over that string:
# dirtiest solution, with unsafe expansions and unquoted variables, not recommended
args=
for arg in $*; do
args="$arg $args"
done
for arg in $args; do
echo "$arg"
done

Bash script called with Variable not respecting Quotes [duplicate]

This question already has answers here:
Variable containing multiple args with quotes in Bash
(4 answers)
Closed 4 years ago.
My goal is to call a script with an variable as it's arguments. For example, I have the following two files:
print_args.sh:
echo "Length: $#"
for i in "$#"; do echo "$i"; done
caller.sh:
ARGS="foo \"Hello, World\" bar"
./test.sh $ARGS
When I run:
./print_args.sh foo "Hello, World" bar
print_args.sh get's called with 3 arguments:
Length 3
foo
Hello, World
bar
However, when running it via caller.sh instead I get 4 args:
Length: 4
foo
"Hello,
World"
bar
What's going on here? How can I get caller.sh to perform as expected?
Note: I don't have control over ARGS it's passed in as an environment variable.
How can I get test_caller.sh to perform as expected?
Use an array:
#!/bin/bash
args=( foo "Hello, World" bar )
./test.sh "${args[#]}"
If you pass an unquoted variable, then word splitting occurs on its contents before it is passed to ./test.sh. You are essentially calling:
'./test.sh' 'foo' '"Hello,' 'World"' 'bar'
Where the single quotes are used to indicate the start and end of words. The double quotes have no syntactic meaning, they are just characters in a string at this point.
If you can convince the users of your script to change the format of the environment variable, then you could split it based on a different separator:
ARGS="foo:Hello World:bar"
set -f # disable glob expansion
IFS=: args=( $ARGS )
set +f # re-enable it
Now you have an array args which you can use as above.
One (really nasty, dangerous) way to get your string into an array would be to use the notorious eval:
readarray -t args < <(eval printf '%s\\n' $ARGS)
Now you have an array args which you can use as in the previous examples.
But this is a big security problem:
ARGS="foo \"Hello, World\" bar \$(ls -l)"
Replace ls -l with any command that you want to execute...

Command line args with spaces [duplicate]

This question already has an answer here:
How can I preserve command line spaces in a linux application?
(1 answer)
Closed 3 years ago.
Invoking a shell script with command line arguments containing spaces is generally solved by enclosing the argument in quotes:
getParams.sh 'one two' 'foo bar'
Produces:
one two
foo bar
getParams.sh:
while [[ $# > 0 ]]
do
echo $1
shift
done
However, if a shell variable is first defined to hold the value of the arguments such as:
args="'one two' 'foo bar'"
then why does:
getParams.sh $args
not recognize the single quotes enclosing the grouped arguments? The output is:
'one
two'
'three
four'
How can I store command line arguments containing spaces into a variable so that when getParams is invoked, the arguments are grouped according to the quoted arguments just as in the original example?
Use an array:
args=('one two' 'foo bar')
getParams.sh "${args[#]}"
Using args="'one two' 'foo bar'" wont work, because the single quotes retain their literal value when inside double quotes.
To preserve multiple spaces in the arguments (and also handle special characters such as *), you should quote your variable:
while [[ $# -gt 0 ]]
do
echo "$1"
shift
done

pass string as arguments with spaces to bash function

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'

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