What is the difference between ${var:-word} and ${var-word}? - bash

I found the following command in a bash script:
git blame $NOT_WHITESPACE --line-porcelain "${2-#}" -- "$file"
What does this ${2-#} mean? Trying out, it returns the 2nd argument, and "#" if it doesn't exist. According to the documentation, ${2:-#} should do the same. I tried it, and it indeed does the same. What's the difference? Where is it documented? The man page does not seem to say anything about this notation.

From Bash hackers wiki - parameter expansion:
${PARAMETER:-WORD}
${PARAMETER-WORD}
If the parameter PARAMETER is unset (never was defined) or null
(empty), this one expands to WORD, otherwise it expands to the value
of PARAMETER, as if it just was ${PARAMETER}. If you omit the :
(colon), like shown in the second form, the default value is only used
when the parameter was unset, not when it was empty.
echo "Your home directory is: ${HOME:-/home/$USER}."
echo "${HOME:-/home/$USER} will be used to store your personal data."
If HOME is unset or empty, everytime you want to print something
useful, you need to put that parameter syntax in.
#!/bin/bash
read -p "Enter your gender (just press ENTER to not tell us): " GENDER
echo "Your gender is ${GENDER:-a secret}."
It will print "Your gender is a secret." when you don't enter the
gender. Note that the default value is used on expansion time, it is
not assigned to the parameter.

Related

How does the "read" command work in Bash functions

Just starting to use bash for the first time (so apologies if this is a dumb question).
As far as I've understood, read is used to receive user input. However, in the example below, it seems that it's also being used to assign function arguments.
Am I missing something here? Is there something else going on? I'm finding it hard to find documentation about how this works.
Any help would be appreciated
function server () {
while true
do
read method path version
if $method = 'GET'
then
echo 'HTTP/1.1 200 OK'
else
echo 'HTTP/1.1 400 Bad Request'
fi
done
}
In your example, the read command is used to read input into 3 different variables method, path and version.
If the user enters as input the line "my name is joe", then the values of method, path and version are:
method -> "my"
path -> "name"
version -> "is joe"
If, however, the user enters "hello world", then only method and path will contain a string. Specifically:
method -> "hello"
path -> "world"
version ->
If you want to use the read command to read input into N variables, i.e. read var1 var2 ... varN, the user's input on the command line will be split/ delimited by the characters in the IFS variable. In fact, IFS stands for "Input Field Separators".
By default, the IFS variable is equivalent to $' \t\n'. This means that unless otherwise specified, user inputs are delimited by space, tab or the newline character.
EDIT: IFS is also commonly referred to as the "Internal Field Separator" as David pointed out in the comments (if you see this from other developers).

What does this bash mean: USER=${1:-`id -un`}

I can not understand this line in one of old scripts in my project :
USER=${1:-`id -un`}
It's a bash parameter expansion pattern.
The given statement indicates:
If the value of $1 (the first positional parameter (argument)) is unset or null then the output of the command id -un will be set as variable USER
If the parameter $1 is set and not null then the expansion of $1 will be set as parameter USER.
Also the variable USER should be set session wide upon login, unless you have a very good reason you should not modify it directly. You can use a different variable name in your script as a solution then.
Check the Parameter Expansion section of man bash to get more idea.
Looking at the manual page for id (man id), the command id -un will return the actual name of the user.
The format :- uses what's on the right only if what's on the left isn't set. More can be learned about this syntax here.
Therefore, the code you provided is essentially saying default to user, but override the $USER variable to the value of $1 if it is provided.
Parse it this way:
The ${} tells you it is a parameter expansion
The :- is use a default value of the Right Hand of the :- if the Left Hand is unset
The backticks around the RH runs that command
Example:
$ sv="set v"
$ echo ${sv:-`whoami`}
set v
$ echo ${not_set:-`whoami`}
dawg
There are other forms, including:
${parameter:?word} display an error if the parameter is unset,
$ echo ${not_set:?'is not set to anything'}
-bash: not_set: is not set to anything
${parameter:+word} substitute value of word only if parameter is set
$ echo ${sv:+'substitute for sv'}
substitute for sv
${parameter:=word} assign parameter the value of word if parameter is unset
$ echo ${not_set:='now it IS set'}
now it IS set
$ echo "$not_set"
now it IS set
That line is setting the value of a variable called USER:
USER=....
The right side of the equal has several levels.
A "parameter expansion", the ${ }.
The parameter being expanded is $1. That's why it is ${1 ... }.
The value of $1 is being tested if it is NUL or unset, the ${1:- }.
If there is a value of $1 (other than nul), that value is substituted.
If $1 is NUL or unset, substitute the value at the rigth of the -.
The value between the - and the } is a "command substitution".
The "command execution" executes a command and replace its output.
The command is id -un. You can understand what it does with man id.
The command id -un prints a name. The current user if user is omitted.
The same command could be executed by $(id -un).
That would change the line to this:
$ USER=${1:-$(id -un)}
Which will set USER to the name given as positional parameter 1 or the name of the current user executing the line.
To try it, you can make a function:
$ tf(){ USER=${1:-$(id -un)}; echo "$USER"; }
If an value is given, that value is printed back:
$ tf bind
bind
If a user-name is not given, the executing user name is printed:
$ tf
bize

Read input, save it to a dynamically-named variable and check if given input was empty

Consider a generic ask() function that asks the user a question, reads the input and saves it in a variable named according to one of the function's arguments.
ask() {
local question="$1"
local varname="$2"
echo "$question"
read $varname
}
Suppose I want to ask the user what is his favourite pet and store the answer in a variable named $pet. Usage would be as follows:
ask "What is your favourite pet?" pet
What I want to do and need help with is check if the user's input was empty, and in that case set the user's input to some string. I would be able to do this easily if the name of the variable the user's input is stored in was constant, like so:
if [ -z "$pet" ]; then
pet="foo"
fi
However the name of the variable I want to check whether or not is empty is whatever I pass in as the second argument. How can I check if the variable (named as per the value of $varname) containing the user's input is empty? The solution should be as portable and standard as possible, and must work under bash and zsh specifically.
In bash, ${!varname} gives you the value of the variable whose name is the value of $varname, but as far as I know, this syntax is not supported by zsh. If you want something that works in both bash and zsh, you may have to use the oldfashioned eval value=\${$varname} and then check $value. You should only use this if you know in advance that the value of $varname is a legal variable name; otherwise this is unsafe.
maybe:
ask() {
name=$1;shift
read -r -p "$# >" var
eval "$name='$var'"
}
ask pet "What is your favourite pet?"
pet=${pet:-foo}
echo "PET: $pet"
Based on the input thus far I managed to get a satisfying solution.
eval varname_tmp=\$$varname
if [ -z "$varname_tmp" ]; then
eval "$varname=foo"
fi

Strange shell behaviour

Here is a simple bash script:
a="asd"
b="qf"
echo "$a.$b"
echo "$a_$b"
It's output is:
asd.qf
qf
Why the second line is not "asd_qf" but "qf"?
Because you haven't defined a variable named a_. For that second printout to work, use:
echo "${a}_$b"
Your second echo displays the value of variable $a_ which is unset.
Use echo "${a}_$b"
The shell has rules about what can go in a variable name, and $a_ is interpreted as the variable named a_ (there is no variable with that name so its value is empty).
You can always add braces to be explicit. In this case, ${a}_$b will clearly identify what the variable name is and the result will be what you expect.

Bash script what is := for?

Does anyone know what is := for?
I tried googling but it seems google filters all symbol?
I know the below is something like checking if the variable HOME is a directory and then something is not equal to empty string.
if [ "${HOME:=}" != "" ] && [ -d ${HOME} ]
From Bash Reference Manual:
${parameter:=word}
If parameter is unset or null, the expansion of word is assigned to
parameter. The value of parameter is
then substituted. Positional
parameters and special parameters may
not be assigned to in this way.
Basically it will assign the value of word to parameter if and only if parameter is unset or null.
From the Bash man page:
Assign Default Values. If
parameter is unset or null, the
expansion of word is assigned to
parameter. The value of parameter
is then substituted. Positional
parameters and special parameters may
not be assigned to in this way.
Man pages are a wonderful thing. man bash will tell you almost everything you want to know about Bash.

Resources