Passing arguments to another script, after having used the "shift" builtin - bash

Consider a simple script program.sh:
$ cat program.sh
echo program: I have "$#" arguments, they are "$#"
$ ./program.sh 1 2 3
program: I have 3 arguments, they are 1 2 3
$ ./program.sh '1 1' 2 3
program: I have 3 arguments. they are 1 1 2 3
Now I want a wrapper script wrapper.sh that invokes program.sh in the same way it was invoked:
$ cat wrapper.sh
echo wrapper: I have "$#" arguments, they are "$#"
./program.sh "$#"
$ ./wrapper.sh 1 2 3
wrapper: I have 3 arguments, they are 1 2 3
program: I have 3 arguments, they are 1 2 3
$ ./wrapper.sh '1 1' 2 3
wrapper: I have 3 arguments. they are 1 1 2 3
program: I have 3 arguments. they are 1 1 2 3
This works perfectly, but I want to use the shift builtin within wrapper.sh, which creates a problem because then I cannot use "$#" anymore in the invocation to program.sh:
$ cat wrapper.sh
echo wrapper: I have "$#" arguments, they are "$#"
shift
./program.sh "$#"
$ ./wrapper.sh 1 2 3
wrapper: I have 3 arguments, they are 1 2 3
program: I have 2 arguments, they are 2 3
$ ./wrapper.sh '1 1' 2 3
wrapper: I have 3 arguments. they are 1 1 2 3
program: I have 2 arguments. they are 2 3
I have tried initially setting the value of $# to a temporary variable, but nothing I tried seems to work for wrapper.sh to invoke program.sh exactly the way it was originally invoked. What is the proper way to handle this?

It is easy to save $#. Just save it as an array:
$ cat wrapper.sh
echo wrapper: I have "$#" arguments, they are "$#"
save=("$#")
shift
./program.sh "${save[#]}"
This produces the output:
$ wrapper.sh '1 1' 2 3
wrapper: I have 3 arguments, they are 1 1 2 3
program: I have 3 arguments, they are 1 1 2 3
The key here is that save is a bash array. Trying to store $# as a shell string does not work. Saving it as an array does work.

Related

List of nums (column) in variable Bash

I want to create code that creates a variable containing the number of positional arguments as a consecutive list.
For example, if the number of positional arguments ($#) is 5 then:
var='
1
2
3
4
5'
Use seq
echo "$#"
5
var=$(seq 1 $#)
echo "$var"
1
2
3
4
5
Try it online!

What is the bash equivalent to batchs' %*

If I have a batch file and want to just use all given script arguments I can use %*.
Example a.bat
echo %*
Calling a.bat 1 2 3 4 gives:
1 2 3 4
how can I do the same in a bash script?
"$*" will return a single string with all the arguments separated by space.
"$#" will return N strings, one for each argument.
Example a.sh:
echo "$#"
Calling a.sh 1 2 3 4 gives:
1 2 3 4
Another example of a.sh:
printf "%s\n" "$#"
Calling a.sh 1 '2 2' '3 3 3' '4 4 4 4' gives:
1
2 2
3 3 3
4 4 4 4
As opposed to a.sh being:
printf "%s\n" "$*"
which will print:
1 2 2 3 3 3 4 4 4 4

convert inputs arguments to string with spaces [duplicate]

In shell scripts, what is the difference between $# and $*?
Which one is the preferred way to get the script arguments?
Are there differences between the different shell interpreters about this?
From here:
$# behaves like $* except that when quoted the arguments are broken up properly if there are spaces in them.
Take this script for example (taken from the linked answer):
for var in "$#"
do
echo "$var"
done
Gives this:
$ sh test.sh 1 2 '3 4'
1
2
3 4
Now change "$#" to $*:
for var in $*
do
echo "$var"
done
And you get this:
$ sh test.sh 1 2 '3 4'
1
2
3
4
(Answer found by using Google)
A key difference from my POV is that "$#" preserves the original number
of arguments. It's the only form that does. For that reason it is
very handy for passing args around with the script.
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
With $# each parameter is a quoted string. Otherwise it behaves the same.
See: http://tldp.org/LDP/abs/html/internalvariables.html#APPREF

How to remove nth element from command arguments in bash

How to I remove the nth element from an argument list in bash?
Shift only appears to remove the first n, but I want to keep some of the first. I want something like:
#!/bin/sh
set -x
echo $#
shift (from position 2)
echo $#
So when I call it - it removes "house" from the list:
my.sh 1 house 3
1 house 3
1 3
Use the set builtin and shell parameter expansion:
set -- "${#:1:1}" "${#:3}"
would remove the second positonal argument.
You could make it generic by using a variable:
n=2 # This variable denotes the nth argument to be removed
set -- "${#:1:n-1}" "${#:n+1}"
a=$1
shift; shift
echo $a $#
If someone knows how to do this in a better way I am all ears!
If you want to use bash you can use a bash array
#!/bin/bash
arg=($0 $#)
or if you don't need argument $0
arg=($#)
# argument to remove
rm_arg=2
arg=(${arg[#]:0:$rm_arg} ${arg[#]:$(($rm_arg + 1))})
echo ${arg[#]}
Now instead of referencing $1 you might use ${arg[1]} .
Remember that bash arguments has an argument $0 and bash arrays have an element [0]. So, if you don't assign bash argument $0 to the first element of a bash array [0] , Then you will find bash argument $1 in your bash array [0] .
arg=($#)
for VAR in $#; do
case $VAR in
A)
echo removing $VAR
arg=($(echo $#| sed "s/$VAR//"))
;;
*)
echo ${arg[#]}
;;
esac
done
Results:
./test.sh 1 2 3 A 4 5
1 2 3 A 4 5
1 2 3 A 4 5
1 2 3 A 4 5
removing A
1 2 3 4 5
1 2 3 4 5

Weird behavior with the $# variable

As I understand and read it, $# turns into "arg1" "arg2" "arg3".
But it seems to have some weird behavior in some cases when quoted.
 
Test 1, $#
$ function somefunc { sh -c "echo $#"; }
$ somefunc 1 2 3
> + somefunc 1 2 3
> + sh -c 'echo 1' 2 3
> 1
The expected output is 1 2 3, the output I get is 1. Now I see that, for some reason, it seems to only pass echo 1 as command, but why does "echo "1" "2" "3"" turn into it? I would maybe have expected echo  because the " before echo may get closed by the " before 1.
 
Test 2, $*
$ function somefunc { sh -c "echo $*"; }
$ somefunc 1 2 3
> + somefunc 1 2 3
> + sh -c 'echo 1 2 3'
> 1 2 3
This is pretty obvious and works, just to make sure. After all $* passes 1 2 3, not "1" "2" "3".
 
Test 3, $# and $*
$ function somefunc { sh -c "echo $# $*"; }
$ somefunc 1 2 3
> + somefunc 1 2 3
> + sh -c 'echo 1 2 3 1 2 3'
> 1 2 3 1 2 3
This seems to work again weirdly enough. But I don't get how "echo "1" "2" "3" 1 2 3" turns into 'echo 1 2 3 1 2 3' and doesn't follow the "pattern" that only "echo $#"did.
 
Test 4, $# and a string
$ function somefunc { sh -c "echo $# hi"; }
$ somefunc 1 2 3
> + somefunc 1 2 3
> + sh -c 'echo 1' 2 '3 hi'
> 1
This again follows the pattern of "echo $#", even thought, as I see it, it is pretty much the same as "echo $# $*" since $* turns into a string after all.
What also puzzles me is that it turned into 'echo 1' 2 '3 hi', the first test would have suggested 'echo 1' 2 3 hi (without the two ' surrounding 3 hi)
Test 5, $# and $var
$ var="hi"
> + var=hi
$ function somefunc { sh -c "echo $# $var"; }
$ somefunc 1 2 3
> + somefunc 1 2 3
> + sh -c 'echo 1 2 3 hi'
> 1 2 3 hi
This works again. So $# seems to work if another variable follows it.
The tests are also possible with su user -c or bash -c instead of sh -c, so I suppose also every other command that executes the next given argument.
I now seem to have gotten some kind of behavior out of it,
but I do still not understand this behavior.
What am I missing here?
The sh language spec has this to say about $#:
Expands to the positional parameters, starting from one. When the expansion occurs within double-quotes, and where field splitting (see Field Splitting ) is performed, each positional parameter shall expand as a separate field, with the provision that the expansion of the first parameter shall still be joined with the beginning part of the original word (assuming that the expanded parameter was embedded within a word), and the expansion of the last parameter shall still be joined with the last part of the original word. If there are no positional parameters, the expansion of '#' shall generate zero fields, even when '#' is double-quoted.
This means that the string "echo $# $*" where the positional parameters are 1, 2, 3 should expand to "echo 1" "2" "3 1 2 3", so that your test 3 should output the string 1. The fact that bash incorrectly expands the string to "echo 1 2 3 1 2 3" and outputs 1 2 3 1 2 3 is indicative of a bug in bash.
The usual technique for dealing with this (yet another) oddity of sh grammar rules is to only use $# in double quotes when it is a distinct word. In other words, it's okay to write "$#", but don't put $# in double quotes unless it is the only thing inside the double quotes.
Using #BrianSwift's hint, everything makes perfectly sense, except for Test3 (here, it is not the case that we get three arguments with the first having a prefix of 'echo ' and the third having a suffix of ' $*'. Instead, the result is a single argument).
But that may probably be explained with the fact that both $# and $* are "special" variables for which special code in the shell exists. I assume the existence of $* just overrides the special behaviour noticed by #BrianSwift, because $* is evaluated later.
Try swapping the order: somefunc() { sh -x "echo $* $#"; }. You will get the split-with-prefix-and-suffix behaviour again which seems to support my above assumption.
So to conclude, everything can be explained with a reference to the manual except for Test3 whose behaviour is probably a bit "underdocumented".
As I understand and read it, $# turns into "arg1" "arg2" "arg3".
No, "$#" turns into "arg1" "arg2" "arg3". The quotes matter.

Resources