What does $# mean in a shell script? - linux

What does a dollar sign followed by an at-sign (#) mean in a shell script?
For example:
umbrella_corp_options $#

$# is all of the parameters passed to the script.
For instance, if you call ./someScript.sh foo bar then $# will be equal to foo bar.
If you do:
./someScript.sh foo bar
and then inside someScript.sh reference:
umbrella_corp_options "$#"
this will be passed to umbrella_corp_options with each individual parameter enclosed in double quotes, allowing to take parameters with blank space from the caller and pass them on.

$# is nearly the same as $*, both meaning "all command line arguments". They are often used to simply pass all arguments to another program (thus forming a wrapper around that other program).
The difference between the two syntaxes shows up when you have an argument with spaces in it (e.g.) and put $# in double quotes:
wrappedProgram "$#"
# ^^^ this is correct and will hand over all arguments in the way
# we received them, i. e. as several arguments, each of them
# containing all the spaces and other uglinesses they have.
wrappedProgram "$*"
# ^^^ this will hand over exactly one argument, containing all
# original arguments, separated by single spaces.
wrappedProgram $*
# ^^^ this will join all arguments by single spaces as well and
# will then split the string as the shell does on the command
# line, thus it will split an argument containing spaces into
# several arguments.
Example: Calling
wrapper "one two three" four five "six seven"
will result in:
"$#": wrappedProgram "one two three" four five "six seven"
"$*": wrappedProgram "one two three four five six seven"
^^^^ These spaces are part of the first
argument and are not changed.
$*: wrappedProgram one two three four five six seven

These are the command line arguments where:
$# = stores all the arguments in a list of string
$* = stores all the arguments as a single string
$# = stores the number of arguments

The usage of a pure $# means in most cases "hurt the programmer as hard as you can", because in most cases it leads to problems with word separation and with spaces and other characters in arguments.
In (guessed) 99% of all cases, it is required to enclose it in ": "$#" is what can be used to reliably iterate over the arguments.
for a in "$#"; do something_with "$a"; done

Meaning.
In brief, $# expands to the arguments passed from the caller to a function or a script. Its meaning is context-dependent: Inside a function, it expands to the arguments passed to such function. If used in a script (outside a function), it expands to the arguments passed to such script.
$ cat my-script
#! /bin/sh
echo "$#"
$ ./my-script "Hi!"
Hi!
$ put () { echo "$#"; }
$ put "Hi!"
Hi!
* Note: Word splitting.
The shell splits tokens based on the contents of the IFS environment variable. Its default value is \t\n; i.e., whitespace, tab, and newline. Expanding "$#" gives you a pristine copy of the arguments passed. Expanding $# may not. More specifically, any arguments containing characters present in IFS might split into two or more arguments or get truncated.
Thus, most of the time what you will want to use is "$#", not $#.

From the manual:
#
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).

$# is basically use for refers all the command-line arguments of shell-script.
$1 , $2 , $3 refer to the first command-line argument, the second command-line argument, third argument.

They are often used to simply pass all arguments to another program
[root#node1 shell]# ./my-script hi 11 33
hi 11 33
[root#node1

Related

bash echo environment variable containing escaped characters

I have an script that echo the input given, into a file as follows:
echo $# > file.txt
When I pass a sting like "\"" I want it to exactly print "\"" to the file however it prints ".
My question is how can I print all characters of a variable containing a string without considering escapes?
When I use echo in bash like echo "\"" it only prints " while when I use echo '"\""' it prints it correctly. I thought maybe that would be the solution to use single quotes around the variable, however I cannot get the value of a variable inside single quotes.
First, note that
echo $# > file.txt
can fail in several ways. Shellcheck identifies one problem (missing quotes on $#). See the accepted, and excellent, answer to Why is printf better than echo? for others.
Second, as others have pointed out, there is no practical way for a Bash program to know exactly how parameters were specified on the command line. For instance, for all of these invocations
prog \"
prog "\""
prog '"'
the code in prog will see a $1 value that consists of one double-quote character. Any quoting characters that are used in the invocation of prog are removed by the quote removal part of the shell expansions done by the parent shell process.
Normally that doesn't matter. If variables or parameters contain values that would need to be quoted when entered as literals (e.g. "\"") they can be used safely, including passing them as parameters to other programs, by quoting uses of the variable or parameter (e.g. "$1", "$#", "$x").
There is a problem with variables or parameters that require quoting when entered literally if you need to write them in a way that they can be reused as shell input (e.g. by using eval or source/.). Bash supports the %q format specification to the printf builtin to handle this situation. It's not clear what the OP is trying to do, but one possible solution to the question is:
if (( $# > 0 )) ; then
printf -v quoted_params '%q ' "$#" # Add all parameters to 'quoted_params'
printf '%s\n' "${quoted_params% }" # Remove trailing space when printing
fi >file.txt
That creates an empty 'file.txt' when no positional parameters are provided. The code would need to be changed if that is not what is required.
If you run echo \", the function of the backslash in bash is to escape the character after it. This actually enables you to use the double quotes as an argument. You cannot use a backslash by itself; if you want to have a backslash as an argument you need to use another slash to escape that: echo \\
Now if you want to create a string where these things are not escaped, use single quotes: echo '\'
See for a better explanation this post: Difference between single and double quotes in Bash

Bash: using parameter expansion to add variables at front and end simultaneously [duplicate]

How to add suffix and prefix to $#?
If I do $PREFIX/$#/$SUFFIX, I get the prefix and the suffix only in the first parameter.
I would use shell [ parameter expansion ] for this
$ set -- one two three
$ echo "$#"
one two three
$ set -- "${#/#/pre}" && set -- "${#/%/post}"
$ echo "$#"
preonepost pretwopost prethreepost
Notes
The # matches the beginning
The % matches the end
Using double quotes around ${#} considers each element as a separate word. so replacement happens for every positional parameter
Let's create a parameters for test purposes:
$ set -- one two three
$ echo "$#"
one two three
Now, let's use bash to add prefixes and suffixes:
$ IFS=$'\n' a=($(printf "pre/%s/post\n" "$#"))
$ set -- "${a[#]}"
$ echo -- "$#"
pre/one/post pre/two/post pre/three/post
Limitations: (a) since this uses newline-separated strings, it won't work if your $# contains newlines itself. In that case, there may be another choice for IFS that would suffice. (b) This is subject to globbing. If either of these is an issue, see the more general solution below.
On the other hand, if the positional parameters do not contain whitespace, then no change to IFS is needed.
Also, if IFS is changed, then one may want to save IFS beforehand and restore afterward.
More general solution
If we don't want to make any assumptions about whitespace, we can modify "$#" with a loop:
$ a=(); for p in "$#"; do a+=("pre/$p/post"); done
$ set -- "${a[#]}"
$ echo "$#"
pre/one/post pre/two/post pre/three/post
Note: This is essentially a slightly more detailed version of sjam's answer.
John1024's answer is helpful, but:
requires a subshell (which involves a child process)
can result in unwanted globbing applied to the array elements.
Fortunately, Bash parameter expansion can be applied to arrays too, which avoids these issues:
set -- 'one' 'two' # sample input array, which will be reflected in $#
# Copy $# to new array ${a[#]}, adding a prefix to each element.
# `/#` replaces the string that follows, up to the next `/`,
# at the *start* of each element.
# In the absence of a string, the replacement string following
# the second `/` is unconditionally placed *before* each element.
a=( "${#/#/PREFIX}" )
# Add a suffix to each element of the resulting array ${a[#]}.
# `/%` replaces the string that follows, up to the next `/`,
# at the *end* of each element.
# In the absence of a string, the replacement string following
# the second `/` is unconditionally placed *after* each element.
a=( "${a[#]/%/SUFFIX}" )
# Print the resulting array.
declare -p a
This yields:
declare -a a='([0]="PREFIXoneSUFFIX" [1]="PREFIXtwoSUFFIX")'
Note that double-quoting the array references is crucial to protect their elements from potential word-splitting and globbing (filename expansion) - both of which are instances of shell expansions.

Zenity --text with string variable

I'm wondering how I can pass a string containing spaces to Zenity for the text argument as my current method is truncating/failed to evaluate all the text after the first space.
Here is an MVP which shows the issue:
Script
#!/bin/bash
a="test test test"
test_func() {
echo "$#"
$(zenity --info --text "test test")
$(zenity --info --text "$#")
}
test_func ${a}
Output
$> test test test
$> (zenity info window with test test as text)
$> (zenity info window with test) *** should contain "test test test"
Use "$*" instead of "$#".
Manual for "$#" (emphasis mine):
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 expan sion occurs within a word, the expansion of the first parameter is joined with the beginning part of the orig‐ inal 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).
And for "$*":
Expands to the positional parameters, starting from one. When the expansion is not within double quotes, each positional parameter expands to a separate word. In contexts where it is performed, those words are subject to further word splitting and pathname expansion. 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.
Alternatively, you could quote ${a} to pass a as a single argument instead of splitting on spaces.

decrypting a variable in a scripting environment of Linux

What does $# in unix shell script signify. For example:
A__JOB="$CLASS $#"
where $CLASS has my java class file name. So what might be the meaning of
$#.
What did I do?
I Googled :) but $# seems to be complex query for ir or maybe i do not know how to search google for special characters.
$# is the value of all arguments passed.
For example, if you pass:
./script A B C D
then "$#" will be equal to "A" "B" "C" "D"
So it looks like the purpose is to passe all the arguments passed to the script directly to the java program.
From bash manual:
# 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).

shell quoting behavior

I just learned that, quoting make a huge difference in some cases, and I did something to test it, here is what I just did,
$ xfs=$(find . -type f -perm -111) #find all files with x-perm
$ echo "$xfs"
./b.out
./a.out
$ echo $xfs
./b.out ./a.out #why all in one line, but the above takes two?
If $xfs contains \n, AFAIK, echo -e will expand \n, but how can echo "$xfs" take 2 lines?
Any whitespace is normally in shell considered to be an argument separator. Thus, your first example has two arguments. echo prints the arguments separated by one space, and that's the behaviour you see in your second example.
However, when you use quotes, anything between them is one argument, and it is printed literally. The one argument in your first example already contains a newline, so it is printed with a newline.
The -e option from the bash echo builtin regulates the expansion of escape sequences like \n; however, you don't have any escape sequences. The variable contains a literal newline.

Resources