Bash: How to convert an array-like-string to array [duplicate] - linux

This question already has answers here:
Convert a Python Data list to a bash array
(5 answers)
Closed 7 years ago.
I have an array-like-string '[ "tag1", "tag2", "tag3" ]' and need to convert it to a bash array. How can this be achieved?

As you know, arrays are declared in bash as follows:
arr=(one two three elements)
So let's try to tranform your input into a bash array. Without parsing the input, this might be very error prone and due to the use of eval, considerably insecure when variable user input would be processed.
Nevertheless, here's a starting point:
t='[ "tag1", "tag2", "tag3" ]'
# strip white space
t=${t// /}
# substitute , with space
t=${t//,/ }
# remove [ and ]
t=${t##[}
t=${t%]}
# create an array
eval a=($t)
When run on a console, this yields:
$ echo ${a[2]}
tag3

The source format is similar to python list.
Using python for intermediate processing:
$ src='[ "tag1", "tag2", "tag,\" 3" ]' # With embedded double quotes, spaces and comma for example.
$ IFS=$'\n' bash_array=($(python <<< 'a='"$src"$'\n''for t in a: print(t)'))
$ printf "%s\n" ${bash_array[#]}
tag1
tag2
tag,"3

Related

How to loop names with spaces in bash script?

I'm having a python list output:
names=[Raj Sonakshi, Getendar, Raghu Varan (Mr)]
I run python script using the below bash command
arr=$(python names.py)
Output I got is:
Raj Sonakshi Getendar Raghu Varan (Mr)
When I run for loop each word is printing instead of full name:
for i in $arr;
do
echo $i
done
Output:
Raj
Sonakshi
.....
.....
Expected Output is :
Raj Sonakshi
Put the names in the array in quotes
names=["Raj Sonakshi", "Getendar", "Raghu Varan (Mr)"]
Not sure what your python script's output looks like exactly; the "output" in your question seems to be after bash has turned it into one long string. And you probably don't want it to be a string (as you are doing with arr=$()), but rather you probably want to use a bash array:
declare -a arr
readarray -t arr <<< "$(python names.py)"
for i in "${arr[#]}"; do
echo "$i"
done

How to transform a string (with a fixed delimiter) into an array to be appended to a JSON?

I've some string inputted by user such as:
read -p "define module tags (example: TAG1, TAG2): " -r module_tags
if [ "$module tags" = "" ]; then module tags="TAG1, TAG2"; fi
which are tags separated by ,
Than, I need to append these tags in a JSON array field:
{
"modules": [
{
"tags": "<user-custom-tags>"
}
]
}
I would do it in this way:
args='.modules[0].tags = '$module_tags''
tmp=$(mktemp -u)
jq --indent 4 "$args" $plugin_file > "$tmp" && mv "$tmp" $plugin_file
But for this, I need to transform the input TAG1, TAG2 to [ "TAG1", "TAG2" ]
How would you do this?
for tag in $module_tags_array
This splits the value of $module_tags_array on IFS, i.e. you are looping over first TAG1, and then TAG2, not the list of tags.
What you are describing can easily be accomplished with
module_tags="[ $module_tags_array ]"
Notice also that the proper way to echo the value of the variable is with quotes:
echo "$module_tags"
unless you specifically require the shell to perform whitespace tokenization and wildcard expansion on the unquoted value. See also When to wrap quotes around a shell variable?
However, a more natural and obvious solution is to actually use an array to store the values.
tags=("TAG1" "TAG2")
printf "\"%s\", " "${tags[#]}" | sed 's/^/[/;s/, $/]/'
The printf produces a string like
"TAG1", "TAG2",
which we then massage into a JSON array expression with a quick bit of sed postprocessing.
Not using bashism, but using jq command line JSON parser:
<<< "TAG1, TAG2" jq -Rc 'split(", ")'
["TAG1","TAG2"]
-R is for raw input string
-c is for compact JSON output (on 1 line)
As expected, split function turns the input string into different part and put them into a JSON array.
Use the "Search and Replace" feature of Bash's parameter expansion:
input="TAG1, TAG2"
output='[ "'${input//, /\", \"}'" ]'
printf "%s\n" "$output"
But be aware that this is not a proper way to quote.

Bash Shell Scripting: How to Sum the Values in a File With the Use of For Loop and bc Command [duplicate]

This question already has answers here:
Shell command to sum integers, one per line?
(45 answers)
Closed 5 years ago.
I'm currently trying to write a function in my bash script that does the following: Take in a file as an argument and calculate the sum of the numbers within that file. I must make use of a for loop and the bc command.
Example of values in the file (each value on their own line):
12
4
53
19
6
So here's what I have so far:
function sum_values() {
for line in $1; do
#not sure how to sum the values using bc
done
}
Am I on the right track? I'm not sure how to implement the bc command in this situation.
You can do it easily without the need of a for loop.
paste -s -d+ numbers.txt | bc
You are not on track. Why?
You are passing the whole file content as a variable which requires to store the whole file in memory. Not a problem with a 1, 2, 3 example, big no go in real life.
You are iterating over the content of a file using a for in loop assuming that you are iterating over the lines of that file. That is not true, because word splitting will be performed which makes the for in loop literally iterate over words, not lines.
As others mentioned, the shell is not the right tool for it. That's because such kind of processing is very slow with the shell compared to awk for example. Furthermore the shell is not able to perform floating point operations, meaning you can only process integers. Use awk.
Correct would be (with bash, for educational purposes):
# Expects a filename
sum() {
filename=${1}
s=0
while read -r line ; do
# Arithmetic expansion
s=$((s+line))
# Or with bc
# s=$(bc <<< "${s}+${line}")
# With floating point support
# s=$(bc -l <<< "${s}+${line}")
done < "${filename}"
echo "${s}"
}
sum filename
With awk:
awk '{s+=$0}END{print s}' filename
While awk (or other higher level language: perl, python, etc) would be better suited for this task, you are on the right track for doing it the naive way. Tip:
$ x=1
$ y=$(bc <<<"$x + 1")
$ echo $y
2
To do math in bash we surround an operation in $(( ... ))
Here are some examples:
$(( 5 + 5 )) # 10
my_var = $((5 + 5)) # my_var is now 10
my_var = $(($my_var + 5)) # my_var is now 10
Solution to your problem:
function sum_values() {
sum=0
for i in $(<$1); do
sum=$(($sum + $i))
done
echo $sum
}
Note that you could have also done $(cat $1) instead of $(<$1) in the solution above.
Edit: Replaced return $sum with echo $sum

Print a string separated by dots in reverse order [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I need to print a string in reverse order in bash shell.
[deeps#host1:~]echo maps.google.com|rev
moc.elgoog.spam
[deeps#host1:~]
But I need it as "com.google.maps". I need it in general for any string separated by period (.).
It should print in reverse order. How do I do that?
I need the solution in Perl as well.
Split by . then reverse the results, then join them up again.
Perl Command Switches:
-l means "remove/add the newline character from input/output."
-p means "execute the program for each line of input, and print $_ after each execution."
-e means "the following argument is the code of the program to run."
perldoc perlrun for more details.
echo maps.google.com | perl -lpe '$_ = join ".", reverse split /\./;'
output
com.google.maps
It also works if you have a data file with lots of rows.
input
maps.google.com
translate.google.com
mail.google.com
run
perl -lpe '$_ = join ".", reverse split /\./;' input
output
com.google.maps
com.google.translate
com.google.mail
Using a bunch of utils:
$ tr '.' $'\n' <<< 'maps.google.com' | tac | paste -s -d '.'
com.google.maps
This replaces all periods with newlines (tr), then reverses the order of the lines (tac), then pastes the lines serially (paste -s), with the period as the delimiter (-d '.').
Considerably uglier (or just wordier?) in pure Bash:
# Read string into array 'arr', split at periods by setting IFS
IFS=. read -a arr <<< 'maps.google.com'
# Loop over array from the end
for (( i = $(( ${#arr[#]}-1 )); i >= 0; --i )); do
# Append element plus a period to result string
res+=${arr[i]}.
done
# Print result string minus the last period
echo "${res%.}"
$ echo maps.google.com | awk -F. '{for (i=NF;i>0;i--) printf "%s%s",$i,(i==1?"\n":".")}'
com.google.maps
How it works
-F.
This tells awk to use a period as the field separator
for (i=NF;i>0;i--) printf "%s%s",$i,(i==1?"\n":".")
This loops over all fields, starting with the last and ending with the first and printing them, followed by a period (except for the first field which is followed by a newline).
The one tricky part above is (i==1?"\n":"."). This is called a ternary statement. The part before the ? is a logical condition. If the condition is true then the value after the question mark, but before the :, is used. If it is false, then the value after the : is used. In this case, that means that, when we are on the first field i==1, then the statement returns a newline, \n. If we are on any other field, it returns a period, .. We use this to put a period after all the fields except for the first (which, in the output, is printed last). After it, we put a newline.
For more on ternary statements, see the GNU docs.
Solution in Perl:
$str = "maps.google.com";
#arr =split('\.',$str);
print join(".",reverse #arr);
output:
com.google.maps
Split the string on "." and reverse the array. Join the reversed array using ".".

split string to array where length is not fixed (not bash)

How is it possible to split a string to an array?
#!/bin/sh
eg.
str="field 1,field 2,field 3,field 4"
The length of the arrays is various
Have found alot of solutions but they only works in bash
update
This only works if the length of the array has 4 values, but what if it has 10 values?
The for loop doesn't seem to work
arr=$(echo "field 1,field 2,field 3,field 4" | awk '{split($0,a,","); print a[1],a[2],a[3],a[4]}');
for value in ${arr[#]}
do
echo "$value\n"
done
To get the split into variables in dash (that doesn't support arrays) use:
string="field 1,field 2,field 3,field 4"
IFS=","
set -- $string
for val
do
echo "$val"
done
In bash you can do this:
str="field 1,field 2,field 3,field 4"
IFS=, array=($str)
IFS is the input field separator.
Zsh is much more elegant
array=${(s:,:)str}
You can even do it directly
% for i in "${(s:,:)str[#]}"; do echo $i; done
field 1
field 2
field 3
field 4

Resources