What is the difference between $* and $# - linux

Who can simply explain
what is the difference between $* and $#?
Why there are two variables for same content as above?

There is no difference if you do not put $* or $# in quotes. But if you put them inside quotes (which you should, as a general good practice), then $# will pass your parameters as separate parameters, whereas $* will just pass all params as a single parameter.
Take these scripts (foo.sh and bar.sh) for testing:
>> cat bar.sh
echo "Arg 1: $1"
echo "Arg 2: $2"
echo "Arg 3: $3"
echo
>> cat foo.sh
echo '$* without quotes:'
./bar.sh $*
echo '$# without quotes:'
./bar.sh $#
echo '$* with quotes:'
./bar.sh "$*"
echo '$# with quotes:'
./bar.sh "$#"
Now this example should make everything clear:
>> ./foo.sh arg1 "arg21 arg22" arg3
$* without quotes:
Arg 1: arg1
Arg 2: arg21
Arg 3: arg22
$# without quotes:
Arg 1: arg1
Arg 2: arg21
Arg 3: arg22
$* with quotes:
Arg 1: arg1 arg21 arg22 arg3
Arg 2:
Arg 3:
$# with quotes:
Arg 1: arg1
Arg 2: arg21 arg22
Arg 3: arg3
Clearly, "$#" gives the behaviour that we generally want.
More detailed description:
Case 1: No quotes around $* and $#:
Both have same behaviour.
./bar.sh $* => bar.sh gets arg1, arg2 and arg3 as separate arguments
./bar.sh $# => bar.sh gets arg1, arg2 and arg3 as separate arguments
Case 2: You use quotes around $* and $#:
./bar.sh "$*" => bar.sh gets arg1 arg2 arg3 as a single argument
./bar.sh "$#" => bar.sh gets arg1, arg2 and arg3 as a separate arguments
More importantly, $* also ignores quotes in your argument list. For example, if you had supplied ./foo.sh arg1 "arg2 arg3", even then:
./bar.sh "$*" => bar.sh will still receive arg2 and arg3 as separate parameters!
./bar.sh "$#" => will pass arg2 arg3 as a single parameter (which is what you usually want).
Notice again that this difference occurs only if you put $* and $# in quotes. Otherwise they have the same behaviour.
Official documentation: http://www.gnu.org/software/bash/manual/bash.html#Special-Parameters

Aside from the difference as described in the technical documents, it is best shown using some examples:
Lets assume we have four shell scripts, test1.sh:
#!/bin/bash
rm $*
test2.sh:
#!/bin/bash
rm "$*"
test3.sh:
#!/bin/bash
rm $#
test4.sh:
#!/bin/bash
rm "$#"
(I am using rm here instead of echo, because with echo, one can not see the difference)
We call all of them with the following commandline, in a directory which is otherwise empty:
./testX.sh "Hello World" Foo Bar
For test1.sh and test3.sh, we receive the following output:
rm: cannot remove ‘Hello’: No such file or directory
rm: cannot remove ‘World’: No such file or directory
rm: cannot remove ‘Foo’: No such file or directory
rm: cannot remove ‘Bar’: No such file or directory
This means, the arguments are taken as a whole string, joined with spaces, and then reparsed as arguments and passed to the command. This is generally not helpful when forwarding arguments to another command.
With test2.sh, we get:
rm: cannot remove ‘Hello World Foo Bar’: No such file or directory
So we have the same as for test{1,3}.sh, but this time, the result is passed as one argument.
test4.sh has something new:
rm: cannot remove ‘Hello World’: No such file or directory
rm: cannot remove ‘Foo’: No such file or directory
rm: cannot remove ‘Bar’: No such file or directory
This implies that the arguments are passed in a manner equivalent to how they were passed to the the script. This is helpful when passing arguments to other commands.
The difference is subtle, but will bite you when passing arguments to commands which expect information at certain points in the command line and when spaces take part in the game. This is in fact a good example of one of the many pitfalls of most shells.

see this here :
$# Stores the number of command-line arguments that
were passed to the shell program.
$? Stores the exit value of the last command that was
executed.
$0 Stores the first word of the entered command (the
name of the shell program).
$* Stores all the arguments that were entered on the
command line ($1 $2 ...).
"$#" Stores all the arguments that were entered
on the command line, individually quoted ("$1" "$2" ...).
take an example
./command -yes -no /home/username
so now..
$# = 3
$* = -yes -no /home/username
$# = ("-yes" "-no" "/home/username")
$0 = ./command
$1 = -yes
$2 = -no
$3 = /home/username

They are different when quoted:
$ set "a b" c d
$ echo $#
3
$ set "$*"
$ echo $#
1
$ set "a b" c d
$ echo $#
3
$ set "$#"
$ echo $#
3
Here only the second form preserves the argument count.

Related

How to create a string=$* without arguments $1 and $2

I have a script that takes in several arguments.
I need everything but $1 and $2 in a string.
I have tried this:
message="$*"
words= $(grep -v "$2"|"$3" $message)
but it doesn't work, it gives me the error:
./backup: line 26: First: command not found
Use shift 2 to shift the arguments along (it drops the first n arguments).
If you need "$1" and "$2" for later, save them in variables first.
Note that in shell, assignments to variables cannot have whitespace either side of the =.
First=$1
Second=$2
shift 2
Message=$#
Maybe something like this?
[root#tsekmanrhel771 ~]# cat ./skip1st2.sh
#!/bin/bash
COUNT=0
for ARG in "$#"
do
COUNT=$[COUNT + 1]
if [ ${COUNT} -gt 2 ]; then
RESULT="${RESULT} ${ARG}"
fi
done
echo ${RESULT}
[root#tsekmanrhel771 ~]# ./skip1st2.sh first second third 4 5 6 7
third 4 5 6 7
You can use a subarray:
$ set -- arg1 arg2 arg3 arg4
$ str=${*:3}
$ echo "$str"
arg3 arg4
More often than not, it's good practice to preserve the arguments as separate elements, though, which you can do by using $# and assigning to a new array:
$ arr=("${#:3}")
$ declare -p arr
declare -a arr=([0]="arg3" [1]="arg4")
Notice that in str=${*:3}, quoting isn't necessary, but in arr=("${#:3}"), it is (or the arguments would be split on whitespace).
As for your error message: your command
words= $(grep -v "$2"|"$3" $message)
does the following:
It sets a variable words to the empty string for the environment of the command (because there is a blank after =).
It tries to set up a pipeline consisting of two commands, grep -v "$2" and "$3" $message. The first of these commands would just hang and wait for input; the second one tries to run the contents of $3 as a command; presumably, based on your error message, $3 contains First.
If the pipeline would actually run, its output would be run as a command (again because of the blank to the right of =).

Parsing arguments after taking into a temp variable in ksh shell scripting

I have a shell script as below:
$ cat check.sh
echo "$#"
for i in "$#"; do
echo "$i"
done
If I run the script with command line args, it prints as below:
$ ./check.sh arg1 arg2 "This is a message" arg4
arg1 arg2 This is a message arg4
arg1
arg2
This is a message
arg4
All is well till now.. -- the number of arguments shown are 4
If I take $# into an variable and do the same thing on it, it will behave as below:
$ cat check.sh
VARGS="$#"
echo "$VARGS"
for i in $VARGS; do
echo "$i"
done
$ ./check.sh arg1 arg2 "This is a message" arg4
arg1 arg2 This is a message arg4
arg1
arg2
This
is
a
message
arg4
Here the number of arguments are 7.
The reason that I have taken the arguments in a temp variable is to remove some unwanted args from it and pass it to another application/process.
Can someone let me know how to get the same behavior in this scenario as if we are using "$#"
Thanks for your help in advance.
I'm using ksh93 for this, but it ought to work in ksh88 as well as it also has arrays:
set -A VARGS "$#"
IFS=
for i in ${VARGS[#]}; do
echo "$i"
done
Setting IFS= is necessary, otherwise the "This is a message" string would be chopped up on spaces.

Bash Programming how to call "$i+1"

I am writing a script that will allow me to change a char in a string from "#" to something else, if I call an argument in terminal.
eg if I write
./myprogram testText.txt -r a
the -r argument will remove all "#" from testTxt.txt and replace them with "a"
My problem is I do not know how to write "If -r is $x, $x+1 is the char I want for replacement"
This is purely a syntax problem, I'm a bash noob :P. Here is the part of code I'm trying to work with.
for i in $*
do
if [[ $i = "-r" ]]
then
$customHashChoice=$((i+1))
# ^^^^^ Problematic Line ^^^^
fi
done
Try this:
customHashChoice=($(getopt "r:" "$#" 2>/dev/null))
if [ "${customHashChoice[0]}" == "-r" ]; then
customHashChoice="${customHashChoice[1]}"
else
echo "-r option is missing. Aborting..."
exit 1
fi
Syntax: getopt optstring parameters
From manual: getopt is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for legal options. It uses the GNU getopt(3) routines to do this.
Here, optstring is r:. It means, that the script accepts an option -r & the option takes an argument (implied by :).
The output of getopt "r:" "$#" is as below:
-r <argument to -r option> -- <unmatched parameters>
e.g. for command-line arguments,
./myprogram testText.txt -r a
getopt "r:" "$#" returns
-r a -- testText.txt
This output is stored in array & the second element of array is used, if the first element is equal to -r.
i=1
while [ "$i" -le $# ]
do
if [[ ${!i} = "-r" ]]
then
i=$(($i + 1))
customHashChoice=${!i}
i=$(($i + 1))
continue
fi
# do something useful
i=$(($i + 1))
done
The command line arguments are numbered 1 through $#. The above loops through each of them. If first checks if the current argument is -r and, if so, sets customHashChoice.
In the above, i contains the argument number. So, $i gives the value of i. To access the i'th command line argument, one uses ${!i}.
A more standard approach
The standard way to process command line arguments in shell scripts is getopts. It can handle many options. Here is sample code that that takes an option -r and requires it to have an argument, which is assigned to the shell variable char:
while getopts r: arg ; do case $arg in
r) char="$OPTARG" ;;
:) echo "${0##*/}: Must supply an argument to $OPTARG." ; exit 1 ;;
\?) echo "Invalid option" ; exit 1 ;;
esac
done
shift $(($OPTIND - 1))
echo "I will replace # with $char in file $1"
For getopts to work, the options have to come first. So, your command line would becomes:
./myprogram -r a testText.txt
If this is not acceptable, you can roll your own custom option processor. In the long run, there is some advantage, however, to standardizing on the usual approach.
You could do something like the following:
#!/bin/bash
val=
xval=
fname=$1
while [ "$*" != "" ]; do
case $1 in
"-r") val="${2}"; shift ;;
"-x") xval="${2}"; shift ;;
esac
shift
done
echo ${fname} ${val} ${xval}
Then when you pass the command like so
./myprogram testText.txt -r a
fname will be testText.txt, and the arguments will be parsed (where the -r will pick up a); for any other values you might want to parse, you'll need variable names to assign and test against. The output would be:
testText.txt a
Hope that helps

what does echo ${1+"$#"} mean

From /usr/local/bin/erl
ROOTDIR=/usr/local/lib/erlang
BINDIR=$ROOTDIR/erts-5.9.1/bin
EMU=beam
PROGNAME=`echo $0 | sed 's/.*\///'`
export EMU
export ROOTDIR
export BINDIR
export PROGNAME
exec $BINDIR/erlexec ${1+"$#"}
I know "$#" meams arguments. But {1+"$#"} means what?
From IEEE Std 1003.1 (POSIX 2013), Shell command language:
${parameter:+[word]}
Use Alternative Value. If parameter is unset or null, null shall be substituted; otherwise, the expansion of word (or an empty string if word is omitted) shall be substituted.
I.e., ${1+"$#"} expands to the value of "$#", the command line arguments, except when $1 is not set, i.e. there are no command line arguments, in which case the expression expands to nothing. A simpler script that shows how this works is
echo '"' ${1+"$#"} '"'
If you store this in a file test.sh and run it, you get:
/tmp$ sh test.sh
" "
/tmp$ sh test.sh 1
" 1 "
/tmp$ sh test.sh 1 2
" 1 2 "
(The spaces at the begin and end come from echo.)
larsmans provides an explanation of the semantics, but doesn't clarify why ${1+"$#"} is different than simply "${#}". In a properly behaving shell, "${#}" expands to nothing. That is: foo "$#" should call foo with no arguments if "$#" is empty. In an incorrectly behaving shell, foo "$#" will be invoked with one argument (the empty string). Many historical shells expanded "$#" to the empty string rather than to nothing, but ${1+"$#"} correctly expands to nothing.

run shell script with arguments manipulation

I need to get three arguments by test.ksh script
as the following
./test.ksh 12 34 AN
is it possible to set the argument by counter for example ?
for get_arg 1 2 3
do
my_array[get_arg]=$$get_arg
print ${my_array[get_arg]}
done
in this example I want to get three arguments from the user by loop counter "$$get_arg"
in place of $1 $2 $3
is it possible? and how ?
my_array=("$#")
for i in 0 1 2
do
echo "${my_array[$i]}"
done
This assigns all the arguments to array my_array; the loop then selects the first three arguments for echoing.
If you're sure you want the first three arguments in the array, you could use:
my_array=("$1" "$2" "$3")
If you want the 3 arguments at positions 1, 2, 3 in the array (rather than 0, 1, 2), then use:
# One or the other but not both of the two assignments
my_array=("dummy" "$#")
my_array=("dummy" "$1" "$2" "$3")
for i in 1 2 3
do
echo "${my_array[$i]}"
done
bash has a special variable
$#
which contains the arguments of the script it currently executes. I think this is what your'e looking for:
for arg in $# ; do
# code
done
Edit:
My bad ksh:
for arg;do
print $arg
done
Original Post:
Use shift to iterate through shell script parameters:
# cat test.sh
#!/bin/bash
while [ "$1" != "" ]; do
echo $1
shift
done
test run:
# ./test.sh arg1 monkey arg3
arg1
monkey
arg3
source
Even you don't need in $#, this would work the same:
#!/bin/bash
i=0
for arg; do
my_array[i]="$arg"
echo "${my_array[i]}"
(( i++ ))
done
That is,
if in words is not present, the for command executes the commands
once for each positional parameter that is set, as if in $# had been
specified.

Resources