I have this script:
#!/bin/bash
list="a b c d"
for item in ${list[#]}; do
echo "${item}"
done
When I run it this is the output:
a
b
c
d
This is exactly what I want. However, shellcheck hates this and throws an error:
for item in ${list[#]}; do
^-- SC2068: Double quote array expansions to avoid re-splitting elements.
But, when I double quote the variable the output of the script changes to this:
a b c d
Which is not what I want.
Is shellcheck right and should I modify the way I try to extract the items from the variable, but how? Or should I just tell shellcheck to ignore this?
This is not an array:
list="a b c d"
You're just assigning list to a string of length 7.
To make it a real array:
list=(a b c d)
Then with for item in "${list[#]}", you get the correct result.
For your updated question, you should just use $list instead of ${list[#]}, because list isn't an array.
I was getting this error when using code below:
redhatCatalogs=("certified-operators" "redhat-marketplace")
for catalog in ${redhatCatalogs[#]}; do
...
Notice I am missing the quotes, after adding the quotes, problem was solved:
redhatCatalogs=("certified-operators" "redhat-marketplace")
for catalog in "${redhatCatalogs[#]}"; do
...
So in conclusion, consider the quotes as well!: "${redhatCatalogs[#]}"
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 creating a program that calculates the area of circle, but bash doesnt compile and execute due to error message in the title. Here is my code:
elif [ $num -le 6 ] && [ $num -ge 4 ]
then
read -p "Enter radius: " radius
let areaCirc=("scale=2;3.1416 * ($radius * $radius)"|bc)
echo "Area of the circle is: " $areaCirc
and the error message is:
syntax error near unexpected token '|'
can someone help me?
To send a string to a command via stdin, use a here-string command <<< string, not a pipe.
Command substitution syntax is $(...), not (...).
Don't use let here. Shell arithmetic only supports integers.
areaCirc=$(bc <<< "scale=2;3.1416 * ($radius * $radius)")
let provides arithmetic context, but we have an ambiguity here, because in a let expression, the vertical bar (|) means bitwise or, but in the shell it has also the meaning of a pipe operator. Look at the following examples:
let bc=4
let a=(4+bc) # Sets a to 8
let b=("5+bc") # Sets b to 9
let c=("(2+4)|bc")
This is syntactically correct and sets c to 6 (because 2+4 equals 6, and the bitwise or of 6 and 4 equals 6).
However if we decided to quote only part of the argument, i.e.
let c=("(2+4)"|bc)
or don't quote at all, i.e.
let c=((2+4)|bc)
we get a syntax error. The reason is that the shell, when parsing a command, first separates it into the different commands, which then are strung together. The pipe is such an operator, and the shell thinks that you want to do a let areaCirc=("scale=2;3.1416 * ($radius * $radius)" and pipe the result into bc. As you can see, the let statement is uncomplete; hence the syntax error.
Aside from this, even if you would fix it, your using of let would not work, because you are using a fractional number (3.1416), and let can do only integer arithmetic. As a workaround, either you do the whole calculation using bc, or some other language (awk, perl,...), or, if this is an option, you switch from bash to zsh, where you can do floating point arithmetic in the shell.
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}
I am intending to change a global variable inside a function in BASH, however I don't get a clue about how to do it. This is my code:
CANDIDATES[5]="1 2 3 4 5 6"
random_mutate()
{
a=$1 #assign name of input variable to "a"
insides=${!a} #See input variable value
RNDM_PARAM=`echo $[ 1 + $[ RANDOM % 5 ]]` #change random position in input variable
NEW_PAR=99 #value to substitute
ARR=($insides) #Convert string to array
ARR[$RNDM_PARAM]=$NEW_PAR #Change the random position
NEW_GUY=$( IFS=$' '; echo "${ARR[*]}" ) #Convert array once more to string
echo "$NEW_GUY"
### NOW, How to assign NEW_GUY TO CANDIDATES[5]?
}
random_mutate CANDIDATES[5]
I would like to be able to assign NEW_GUY to the variable referenced by $1 or to another variable that would be pointed by $2 (not incuded in the code). I don't want to do the direct assignation in the code as I intend to use the function for multiple possible inputs (in fact, the assignation NEW_PAR=99 is quite more complicated in my original code as it implies the selection of a number depending the position in a range of random values using an R function, but for the sake of simplicity I included it this way).
Hopefully this is clear enough. Please let me know if you need further information.
Thank you,
Libertad
You can use eval:
eval "$a=\$NEW_GUY"
Be careful and only use it if the value of $a is safe (imagine what happens if $a is set to rm -rf / ; a).
I read somewhere that ksh's array supports += to append new elements, but I tried it and it doesn't work:
[ksh] # arr=(a b c d)
[ksh] # arr+=e
[ksh] # echo ${arr[*]}
ae b c d
[ksh] #
Why does arr[0] becomes ae?
To add an element to the array, it should be like this:
arr+=(e)
By doing arr+=e , it will add to the 1st element of the array. Its because just the name arr points to the 1st element of the array itself:
$ arr=(a b c d)
$ echo ${arr[0]}
a
$ echo $arr
a
It's arr+=(e). Any simple assignment that doesn't specify an index always refers to the zeroth element.
Note that this is a trivial case and things can get more complicated. += has different behavior for both simple and compound assignment depending on context, and also differs between bash, ksh93, and zsh, so it's easy to become confused.
http://wiki.bash-hackers.org/syntax/arrays#storing_values