In almost every language I tend to write something that sets a variable to a default value if it's not defined. Everytime I get surprised why the syntax is not simpler, e.g. why I have to write the variable name twice just to set it to a default value. For example, in Perl:
my $var;
# some code here...
$var = "default" unless $var; # $var typed twice
Or in C:
char *var = NULL;
// some code here...
if (!var) // var typed
var = "default"; // twice
Why not have some syntactic sugar that sets the variable if it's not defined? In perl it could look like
$var ?= "default";
I am just curious, are there any languages out there that in fact have syntactic sugar for this?
bash supports the parameter expansion and testing.
e.g. if $1 is defined AND NOT EMPTY, use $1; otherwise, set to "text", enter:
output=${1-text}
Related
I have a bash function that looks like this:
banner(){
someParam=$1
someOtherParam=$2
precedingParams=2
for i in $(seq 1 $precedingParams);do
shift
done
for i in $(seq 1 $(($(echo ${##}) - $precedingParams)));do
quotedStr=$1
shift
#do some stuff with quotedStr
done
}
This function, while not entirely relevant, will build a banner.
All params, after the initial 2, are quoted lines of text which can contain spaces.
The function fits each quoted string within the bounds of the banner making new lines where it sees fit.
However, each new parameter ensures a new line
My function works great and does what's expected, the problem, however, is in calling the function with dynamic parameters as shown below:
e.g. of call with standard static parameters:
banner 50 true "this is banner text and it will be properly fit within the bounds of the banner" "this is another line of banner text that will be forced to be brought onto a new line"
e.g. of call with dynamic parameter:
banner 50 true "This is the default text in banner" "$([ "$someBool" = "true" ] && echo "Some text that should only show up if bool is true")"
The problem is that if someBool is false, my function will still register the resulting "" as a param and create a new empty line in the banner.
As I'm writing this, I'm finding the solution obvious. I just need to check if -n $quotedStr before continuing in the function.
But, just out of blatant curiosity, why does bash behave this way (what I mean by this is, what is the process through which subshell expansion occurs in relation to parameter isolation to function calls based on quoted strings)
The reason I ask is because I have also tried the following to no avail:
banner 50 true "default str" $([ "$someBool" = "true" ] && echo \"h h h h\")
Thinking it would only bring the quotes down if someBool is true.
Indeed this is what happens, however, it doesn't properly capture the quoted string as one parameter.
Instead the function identifies the following parameters:
default str
"h
h
h
h"
When what I really want is:
default str
h h h h
I have tried so many different iterations of calls, again to no avail:
$([ "$someBool" = "true" ] && echo "h h h h")
$([ "$someBool" = "true" ] && echo \\\"h h h h\\\")
$([ "$someBool" = "true" ] && awk 'BEGIN{printf "%ch h h h h%c",34,34}')
All of which result in similar output as described above, never treating the expansion as a true quoted string parameter.
The reason making the command output quotes and/or escapes doesn't work is that command substitutions (like variable substitutions) treat the result as data, not as shell code, so shell syntax (quotes, escapes, shell operators, redirects, etc) aren't parsed. If it's double-quoted it's not parsed at all, and if it's not in double-quotes, it's subject to word splitting and wildcard expansion.
So double-quotes = no word splitting = no elimination of empty string, and no-double-quotes = word splitting without quote/escape interpretation. (You could do unpleasant things to IFS to semi-disable word splitting, but that's a horrible kluge and can cause other problems.)
Usually, the cleanest way to do things like this is to build a list of conditional arguments (or maybe all arguments) in an array:
bannerlines=("This is the default text in banner") # Parens make this an array
[ "$someBool" = "true" ] &&
bannerlines+=("Some text that should only show up if bool is true")
banner 50 true "${bannerlines[#]}"
The combination of double-quotes and [#] prevents word-splitting, but makes bash expand each array element as a separate item, which is what you want. Note that if the array has zero elements, this'll expand to zero arguments (but be aware that an empty array, like bannerlines=() is different from an array with an empty element, like bannerlines=("")).
I was writing a script where I came across a situation.
Audio_Repo = "/src/audio_123";
Audio_ImgTag = "aud021882";
Audio_Enable = 1;
.....
Video_Repo = "/src/vid_823";
Video_ImgTag = "video9282";
Video_Enable = 0;
....
#Say proj_var ="Audio"
#it could be either Audio or Video based on some conditional check
....
proj_var = "Audio"
....
PROJECT_REPO= ${!{$proj_var"_Repo"}}
#PROJECT_REPO should hold the value "src/audio_123"
But the above representation throws bad substitution error
I know that I could use a temporary variable as follows
temp= $proj_var"_Repo";
PROJECT_REPO = ${!temp};
But I have many properties and I do not want to use temporary variables for each of them. Instead I want single line substitutions.
One way to do it is to use eval:
#! /bin/bash -p
Audio_Repo="/src/audio_123"
Audio_ImgTag=aud021882
Audio_Enable=1
# ...
Video_Repo=/src/vid_823
Video_ImgTag=video9282
Video_Enable=0
# ....
# Say proj_var="Audio"
# it could be either Audio or Video based on some conditional check
# ....
proj_var="Audio"
# ....
eval "Project_Repo=\${${proj_var}_Repo}"
# Project_Repo should hold the value "src/audio_123"
printf '%s\n' "$Project_Repo"
The code prints /src/audio_123.
eval is dangerous, and should be avoided if possible. See Why should eval be avoided in Bash, and what should I use instead?. In this case the temporary variable, despite the increased verbosity, is a better option.
I replaced PROJECT_REPO with Project_Repo to avoid possible a possible clash with an environment variable. See Correct Bash and shell script variable capitalization.
I've fixed some Bash syntax issues in the code in the question. Spaces around = are errors. Semicolons at the ends of lines are unnecessary.
Shellcheck issues some warnings for the code, but they are all harmless.
Another option is to use a helper function:
# ...
# Set the value of the variable whose name is in the first parameter ($1)
# to the value of the variable whose name is in the second parameter ($2).
function setn { printf -v "$1" '%s' "${!2}" ; }
# ...
setn Project_Repo "${proj_var}_Repo"
Using the setn (a poor name, choose a better one) function avoids both a temporary variable and eval.
Uses arrays, not variable names you need to manipulate.
Repo=0
ImgTag=1
Enable=2
Audio=(/src/audio_123 aud021882 1)
Video=(/src/vid_823 video9282 0)
proj_repo=Audio[$Repo]
project_var=${!proj_repo}
Given this bash code:
HELLO =${HELLO:-hello}
the variable HELLO takes a value from the HELLO environment variable if it exists. Otherwise it sets the value to be hello.
What is the Powershell equivalent?
PowerShell, as of Windows PowerShell v5.1 / PowerShell Core 6.1.0, has no equivalent functionality to Bash's parameter expansion feature, of which ${HELLO:-hello} is an instance[1].
Note:
In Bash, environment variables and Bash's own shell variables share the same namespace, and environment variables are automatically exposed as shell variables.
In PowerShell, only PowerShell's own variables can be referenced directly - e.g., $myVar - whereas referencing environment variables requires explicit use of the env: namespace - e.g., $env:PATH
The solutions below focus mostly on PowerShell's own variables, but the techniques can analogously be applied to environment variables.
Note that while environment variables are always strings, PowerShell variables can be of any (.NET) type.
To emulate HELLO=${HELLO:-hello} in PowerShell, use:
# To target an *environment* variable, use $env:HELLO instead.
$HELLO = if ("$HELLO") { $HELLO } else { 'hello' }
Note the "..." around $HELLO in the conditional, which ensures that the value is converted to a string before coercing it to a Boolean[2]: that way, both the case of $HELLO not having been defined (or explicitly containing $null) and the variable containing the empty string evaluate to $False, which parallels Bash's behavior.
Without the stringification with "...", non-string values such as 0 or $False too would trigger the else branch.
However, if you only ever expect $HELLO to contain a string value, if any, you can omit the "...".
Similarly, the above also works with environment variables, but since they are always strings, you don't strictly need the enclosing "..." in that case:
$env:HELLO = if ($env:HELLO) { $env:HELLO } else { 'hello' }
In the simple case of leaving any preexisting value of $HELLO alone and only assigning a default value in the in the absence of the former:
if (-not "$HELLO") { $HELLO = 'hello' }
# As an environment variable
if (-not $env:HELLO) { $env:HELLO = 'hello' }
To emulate HELLO=${HELLO-hello} - note the absence of the : -, use:
$HELLO = if ($null -eq $HELLO) { 'hello' } else { $HELLO }
# Simplified
if ($null -eq $HELLO) { $HELLO = 'hello' }
This covers only the case of $HELLO not being defined (and also it explicitly containing $null, but that isn't common).
Note that the $null is deliberately used as the LHS, which is a good habit to form in PowerShell to avoid surprises if the LHS happens to be an array, in which case -eq acts an array filter rather than returning a Boolean.
[1] While Bash's parameter expansion will likely never be implemented in PowerShell as such, simply because it is not a good syntactic fit for the language, providing concise, PowerShell-idiomatic analogs to Bash's ${HELLO-hello} and ${HELLO=hello} is being discussed, as $HELLO ?? 'hello' and $HELLO ?= 'hello' - see this GitHub issue.
[2] PowerShell coerces any string to a Boolean with this simple rule: if the string is empty, it evaluates to $False; if it is non-empty - whatever its contents - it evaluates to $True.
The most straight forward way is :
$Hello = If($env:hello -eq $null){"WORLD"}else{$env:hello}
Or you can make a alias if you plan on using it a lot
function IfNull($If, $Else){
if($If -eq $Null){
$Else
}else{
$If
}
}
Add-Alias "??" IfNull
$Hello = ?? $env:hello "World2"
The code is below,
if [ -z ${CONFIG+x} ]; then
CONFIG=/usr/local/etc/config.ini
else
CONFIG=$(realpath $CONFIG)
fi
Can someone tell me what "x" exactly mean?
It means that if the variable $CONFIG is set, use the value x, otherwise use the null (empty) string.
The test -z checks whether the following argument is empty, so if $CONFIG is not set, the if branch will be taken.
Testing it out (with $a previously unset):
$ echo "a: '${a+x}'"
a: ''
$ a='any other value'
$ echo "a: '${a+x}'"
a: 'x'
This is one of the parameter expansion features defined for the POSIX shell. The simplest is just a variable which is substituted, ${parameter}, while the others have an embedded operator telling what to do with an optional parameter versus an optional word, e.g, "${parameterOPword}"
For this particular case, parameter is CONFIG, OP is + and word is x:
if the parameter is Set and Not Null, the word's value is used, otherwise
if the parameter is Set and Null, the word's value is used, otherwise
a null value is used
A null value for a shell variable is from an explicit assignment, e.g.,
CONFIG=
If a variable has never been assigned to, it is unset. You can make a variable unset using that keyword:
unset CONFIG
There are separate tests for Set and Not Null and Set and Null because the standard lists eight operators. For six of those, the parameter's value would be used if it is Set and Not Null.
In substitution, you cannot see a difference between an unset and null value. Some shells (such as bash) define additional types of parameter expansion; this question is more general.
I've implemented a function which contains a while for loop defined as follows:
func()
{
...
for i in "$#"; do
enum="source"
sourceID=$i
ret_ID $FILE_NAME $sourceID $enum
ID=$req_ID
((ID+=ID))
done
echo $ID
}
The function ret_ID parses a file which contains some variables as
param1=0x00000001
param2=0x00000400
param3=0x000008000
...
No matter how many parameters I pass, echo $ID returns the ID associated with the last parameter and not the sum of all of them. For instance, func param1 param3 returns 32768 and not 32769.
Update: Judging by a comment by the OP, it sounds like glenn jackman's recommendation to switch from letting ret_ID() set a global variable in order to return its result to outputting its result to stdout and capturing the result in a command substitution ($(...)) solved the problem.
Assuming that the problem wasn't the simple logic error Glenn points out (((ID+=ID)) should be ((ID+=req_ID )): The exact cause of the original problem is not known, but since both func() and ret_ID() operate on global variables, it's easy to see how one function could interfere with the other, such as if ret_ID() accidentally also sets variable $ID.
Here's a rewrite of the function that shows this change, and also suggests a few other changes to make the function more robust, most notably the use of local variables:
func()
{
# Declare *local* variables.
local arg enum sourceID retID
# Declare the local result variable *as an integer*
# Also, better to use variable names that at least start with a *lowercase*
# letter to avoid conflicts with *environment* variables.
local -i id=0
# ...
for arg; do # loop over all args; the `in "$#"` part is optional
enum="source"
sourceID=$arg
# Call helper function and let it return its result via *stdout* captured
# through a command substitution rather than by setting a global variable.
# Note the use of double quotes to prevent problems with values with embedded spaces.
reqID=$(ret_ID "$FILE_NAME" "$sourceID" "$enum")
# Add the value returned to $id
# Note that since $id was declared as an integer,
# use of (( ... )) is optional.
id+=$reqID
done
echo "$id"
}