Can I assign the default values to array in bash? - bash

* I previously asked a question but it was not the correct question. Now I made the correct question and fixed sample code. And I will up an answer which partially cites an answer to the previous question. *
I would like to default value to arrays in bash. Please see following,
function chmod_chown_func() {
local file_path="$1"
local chmod_options[2]=${2:='-R 744'} # This line has error.
local chown_options[2]=${3:='-R root:root'} # This line has error.
sudo chmod "${chmod_options[#]}" "${file_path}"
sudo chown "${chown_options[#]}" "${file_path}"
}
chmod_chown_func "test.txt"
The error message is
$2: cannot assign in this way
Thank you very much.

Parameter Expansions
Yes, the expansion ${a:=default} changes the value of a.
It is called "Assign Default Values" in the bash manual.
$ unset a
$ echo "<${a}> and <${a:=default}>, But <${a}>"
<> and <default>, But <default>
But that syntax could not be applied to positional parameters.
Positional parameters can be (mostly) changed with set.
$ echo "$#"
a b c
$ set -- d e f
$ echo "$#"
d e f
But you can use the expansion of "Use default value" as called in the manual:
$ unset a
$ echo "<${a}> and <${a:-default}>, But <${a}>"
<> and <default>, But <>
To assign value(s) to an array variable.
A common idiom is
$ array=( aaa bbb ccc )
$ echo "${array[1]}"
bbb
Or:
$ declare -a array=( aaa bbb ccc )
Which also will make the variable local to a function if used inside the function.
However, it comes with the detail that wildcards (*, ? and []) will be expanded (unless quoted or the option set -f is used).
Overall, it is better to use read:
$ IFS=' ' read -a array <<<"$a"
Array index
You can not assign a whole array by using one index. This:
chmod_options[2]=${2:-'-R 744'}
Will only create one array value, at index 2. A better way will be:
chmod_options=( ${2:--R 744} )
Or, as explained above:
IFS=' ' read -a chmod_options <<<"${2:--R 744}"

The follwings are error points and an answer code.
Error 1:
The default value by ${variable:='some value'} does not work with positional parameter.
It should be ${variable:-'some value'}
Error 2:
To assign an default value to an array, declare an array and assign a default array value to it.
An example answer code is following
function chmod_chown_func() {
local file_path="$1"
local -a chmod_options=${2:-( -R 744 )}
local -a chown_options=${3:-( -R root:root )}
sudo chmod "${chmod_options[#]}" "${file_path}"
sudo chown "${chown_options[#]}" "${file_path}"
}

I didn't have much luck with the existing answers, so might as well KISS and use a little logic to determine if the array is empty, and then assign the default value if necessary.
local -a chmod_options="$2"
local -a chown_options="$3"
# Assign default value if chmod_options is an empty string or array
[[ -z "$chmod_options" || ${#chmod_options[#]} -eq 0 ]] && chmod_options=('-R 744')
# Assign default value if chown_options is an empty string or array
[[ -z "$chown_options" || ${#chown_options[#]} -eq 0 ]] && chown_options=('root:root')

Related

how to assign each of multiple lines in a file as different variable?

this is probably a very simple question. I looked at other answers but couldn't come up with a solution. I have a 365 line date file. file as below,
01-01-2000
02-01-2000
I need to read this file line by line and assign each day to a separate variable. like this,
d001=01-01-2000
d002=02-01-2000
I tried while read commands but couldn't get them to work.It takes a lot of time to shoot one by one. How can I do it quickly?
Trying to create named variable out of an associative array, is time waste and not supported de-facto. Better use this, using an associative array:
#!/bin/bash
declare -A array
while read -r line; do
printf -v key 'd%03d' $((++c))
array[$key]=$line
done < file
Output
for i in "${!array[#]}"; do echo "key=$i value=${array[$i]}"; done
key=d001 value=01-01-2000
key=d002 value=02-01-2000
Assumptions:
an array is acceptable
array index should start with 1
Sample input:
$ cat sample.dat
01-01-2000
02-01-2000
03-01-2000
04-01-2000
05-01-2000
One bash/mapfile option:
unset d # make sure variable is not currently in use
mapfile -t -O1 d < sample.dat # load each line from file into separate array location
This generates:
$ typeset -p d
declare -a d=([1]="01-01-2000" [2]="02-01-2000" [3]="03-01-2000" [4]="04-01-2000" [5]="05-01-2000")
$ for i in "${!d[#]}"; do echo "d[$i] = ${d[i]}"; done
d[1] = 01-01-2000
d[2] = 02-01-2000
d[3] = 03-01-2000
d[4] = 04-01-2000
d[5] = 05-01-2000
In OP's code, references to $d001 now become ${d[1]}.
A quick one-liner would be:
eval $(awk 'BEGIN{cnt=0}{printf "d%3.3d=\"%s\"\n",cnt,$0; cnt++}' your_file)
eval makes the shell variables known inside your script or shell. Use echo $d000 to show the first one of the newly defined variables. There should be no shell special characters (like * and $) inside your_file. Remove eval $() to see the result of the awk command. The \" quoted %s is to allow spaces in the variable values. If you don't have any spaces in your_file you can remove the \" before and after %s.

Open file with two columns and dynamically create variables

I'm wondering if anyone can help. I've not managed to find much in the way of examples and I'm not sure where to start coding wise either.
I have a file with the following contents...
VarA=/path/to/a
VarB=/path/to/b
VarC=/path/to/c
VarD=description of program
...
The columns are delimited by the '=' and some of the items in the 2nd column may contain gaps as they aren't just paths.
Ideally I'd love to open this in my script once and store the first column as the variable and the second as the value, for example...
echo $VarA
...
/path/to/a
echo $VarB
...
/path/to/a
Is this possible or am I living in a fairy land?
Thanks
You might be able to use the following loop:
while IFS== read -r name value; do
declare "$name=$value"
done < file.txt
Note, though, that a line like foo="3 5" would include the quotes in the value of the variable foo.
A minus sign or a special character isn't allowed in a variable name in Unix.
You may consider using BASH associative array for storing key and value together:
# declare an associative array
declare -A arr
# read file and populate the associative array
while IFS== read -r k v; do
arr["$k"]="$v"
done < file
# check output of our array
declare -p arr
declare -A arr='([VarA]="/path/to/a" [VarC]="/path/to/c" [VarB]="/path/to/b" [VarD]="description of program" )'
What about source my-file? It won't work with spaces though, but will work for what you've shared. This is an example:
reut#reut-home:~$ cat src
test=123
test2=abc/def
reut#reut-home:~$ echo $test $test2
reut#reut-home:~$ source src
reut#reut-home:~$ echo $test $test2
123 abc/def

How to get a last key of an associative array (dictionary)

How I can have an access to the last key of the associative array in bash? In this example I need to have "lot" in the $last variable. I found a way described here: How to get the keys and values of an associative array indirectly in Bash?. But it doesn't work as expected in the example below and return this error:
./test.sh: line 9: keys2: ${!$addict[#]}: must use subscript when assigning associative array
Here are the contents of this test.sh:
declare -A addict=(
["foo"]="bar"
["few"]="baz"
["lot"]="pot"
)
index_last=$(( ${#addict[#]} - 1 ))
eval 'declare -A keys2=(${!$addict[#]})'
last="${keys2[$index_last]}"
echo "$keys2"
echo "$index_last"
echo "$last"
While Tom Fenech is absolutely right saying
The keys are unordered, so the concept of a "last key" doesn't really make sense, you can avoid the error by changing the line with eval to
keys2=( "${!addict[#]}" )
and see what you get. It may also be illuminating to look at declare -p addict. In order to get some key (first key that is returned from unordered keys list, not first key that was declared) you can do:
some_key="${keys2[0]}"
This way you could, for example, unset A[$some_key] and iterate over keys in such manner by picking first returned key each time.
Example:
$ declare -A A=( [a]=x [b]=y [c]=z )
$ echo "${!A[#]}"
c b a
$ keys=( "${!A[#]}" )
$ echo "${keys[0]}"
c
# you see that c was returned instead of a
# (on your computer order could be different)
$ unset A[$some_key]
$ echo "${!A[#]}"
b a
$ declare -p A
declare -A A=([b]="y" [a]="x" )
Further reading: GNU Bash - Arrays.

Unable to set second to last command line argument to variable

Regardless of the number of arguments passed to my script, I would like for the second to the last argument to always represent a specific variable in my code.
Executing the program I'd type something like this:
sh myprogram.sh -a arg_a -b arg_b special specific
test=("${3}")
echo $test
The results will show 'special'. So using that same idea if I try this (since I won't know that number of arguments):
secondToLastArg=$(($#-1))
echo $secondToLastArg
The results will show '3'. How do I dynamically assign the second to last argument?
You need a bit of math to get the number you want ($(($#-1))), then use indirection (${!n}) to get the actual argument.
$ set -- a b c
$ echo $#
a b c
$ n=$(($#-1))
$ echo $n
2
$ echo ${!n}
b
$
Indirection (${!n}) tells bash to use the value of n as the name of the variable to use ($2, in this case).
You can use $# as array & array chopping methods:
echo ${#:$(($#-1)):1}
It means, use 1 element starting from $(($#-1))...
If some old versions of shells do not support ${array:start:length} syntax but support only ${array:start} syntax, use below hack:
echo ${#:$(($#-1))} | { read x y ; echo $x; } # OR
read x unused <<< `echo ${#:$(($#-1))}`

Creating a string variable name from the value of another string

In my bash script I have two variables CONFIG_OPTION and CONFIG_VALUE which contain string VENDOR_NAME and Default_Vendor respectively.
I need to create a variable with name $CONFIG_OPTION ie VENDOR_NAME and assign the value in CONFIG_VALUE to newly created variable.
How I can do this?
I tried
$CONFIG_OPTION=$CONFIG_VALUE
But I am getting an error on this line as
'./Build.bash: line 137: VENDOR_NAME="Default_Vendor": command not found'
Thanks.
I know that nobody will mention it, so here I go. You can use printf!
#!/bin/bash
CONFIG_OPTION="VENDOR_NAME"
CONFIG_VALUE="Default_Vendor"
printf -v "$CONFIG_OPTION" "%s" "$CONFIG_VALUE"
# Don't believe me?
echo "$VENDOR_NAME"
This uses bash builtins:
#!/bin/bash
VAR1="VAR2"
declare "${VAR1}"="value"
echo "VAR1=${VAR1}"
echo "VAR2=${VAR2}"
The script output:
VAR1=VAR2
VAR2=value
Here's the snippet using your variable names:
#!/bin/bash
CONFIG_OPTION="VENDOR_NAME"
declare "${CONFIG_OPTION}"="value"
echo "CONFIG_OPTION=${CONFIG_OPTION}"
echo "VENDOR_NAME=${VENDOR_NAME}"
The script output:
CONFIG_OPTION=VENDOR_NAME
VENDOR_NAME=value
For pure shell, possibly try:
#!/usr/bin/env sh
option=vendor_name
value="my vendor"
eval $option="'$value'" # be careful with ', \n, and \ in value
eval echo "\$$option" # my vendor
echo "$vendor_name" # my vendor
Why?
#!/usr/bin/env sh
printf -v "var" "val" # prints the flag, var not set
declare var=val # sh: declare: not found
echo ${!var} # sh: syntax error: bad substitution
I don't like eval, but are there any POSIX options?
Existing answers are great for strings, but do not work with arrays.
Here is a working solution for arrays:
ARRAY=(a b c d e f)
array_name="ARRAY_NAME"
TARGET_ARRAY="${array_name}[#]" # ARRAY="ARRAY[#]"
TARGET_ARRAY=("${!TARGET_ARRAY}") # ARRAY=(a b c d e f)
the array_name string can also have * suffix to obtain the array elements.

Resources