How to format a bash array as a JSON array - arrays

I have a bash array
X=("hello world" "goodnight moon")
That I want to turn into a json array
["hello world", "goodnight moon"]
Is there a good way for me to turn this into a json array of strings without looping over the keys in a subshell?
(for x in "${X[#]}"; do; echo $x | sed 's|.*|"&"|'; done) | jq -s '.'
This clearly doesn't work
echo "${X[#]}" | jq -s -R '.'

You can do this:
X=("hello world" "goodnight moon")
printf '%s\n' "${X[#]}" | jq -R . | jq -s .
output
[
"hello world",
"goodnight moon"
]

Since jq 1.6 you can do this:
jq --compact-output --null-input '$ARGS.positional' --args -- "${X[#]}"
giving:
["hello world","goodnight moon"]
This has the benefit that no escaping is required at all. It handles strings containing newlines, tabs, double quotes, backslashes and other control characters. (Well, it doesn't handle NUL characters but you can't have them in a bash array in the first place.)

This ...
X=("hello world" "goodnight moon" 'say "boo"' 'foo\bar')
json_array() {
echo -n '['
while [ $# -gt 0 ]; do
x=${1//\\/\\\\}
echo -n \"${x//\"/\\\"}\"
[ $# -gt 1 ] && echo -n ', '
shift
done
echo ']'
}
json_array "${X[#]}"
... yields:
["hello world", "goodnight moon", "say \"boo\"", "foo\\bar"]
If you are planning to do a lot of this (as your reluctance to use a subshell suggests) then something such as this that does not rely on any subprocess is likely to your advantage.

You can use:
X=("hello world" "goodnight moon")
sed 's/^/[/; s/,$/]/' <(printf '"%s",' "${X[#]}") | jq -s '.'
[
[
"hello world",
"goodnight moon"
]
]

If the values do not contain ASCII control characters, which have to be escaped in strings in valid JSON, you can also use sed:
$ X=("hello world" "goodnight moon")
$ printf %s\\n "${X[#]}"|sed 's/["\]/\\&/g;s/.*/"&"/;1s/^/[/;$s/$/]/;$!s/$/,/'
["hello world",
"goodnight moon"]
If the values contain ASCII control characters, you can do something like this:
X=($'a\ta' $'a\n\\\"')
for((i=0;i<${#X[#]};i++));do
[ $i = 0 ]&&printf \[
printf \"
e=${X[i]}
e=${e//\\/\\\\}
e=${e//\"/\\\"}
for((j=0;j<${#e};j++));do
c=${e:j:1}
if [[ $c = [[:cntrl:]] ]];then
printf '\\u%04x' "'$c"
else
printf %s "$c"
fi
done
printf \"
if((i<=${#X[#]}-2));then
printf ,
else
printf \]
fi
done

As improve of answer
https://stackoverflow.com/a/26809278/16566807
Script produce few formats which could be usefull when include. Script meets BASH spec, checked with shellcheck.
#!/bin/bash
#
#
X=("hello world" "goodnight moon" 'say "boo"' 'foo\bar')
#
# set parameter to define purpose: return_format
# php5 -> for 5.x
# -> https://stackoverflow.com/questions/7073672/how-to-load-return-array-from-a-php-file/7073686
# php -> for 7.x and greater
# json -> for $array=#file_get_contents($f); json_decode($array, true);
# /none/ -> for JS to JSON.Parse(myJSON);
# function call with array as parameter: return_array "${array[#]}"
return_array() {
rf="${return_format}"
if [[ $rf = "php5" ]]; then
q=("<?php return array(" ");")
elif [[ $rf = "php" ]];then
q=("<?php return [" "];")
elif [[ $rf = "json" ]];then
q=("{" "}")
else
q=("[" "]")
fi
echo -n "${q[0]}"
while [[ $# -gt 0 ]]; do
x=${1//\\/\\\\}
echo -n "\"${x//\"/\\\"}\""
[[ $# -gt 1 ]] && echo -n ', '
shift
done
echo "${q[1]}"
}
echo "PHP 5.x"
return_format="php5"
return_array "${X[#]}"
echo "PHP 7.x"
return_format="php"
return_array "${X[#]}"
echo "JSON for PHP"
return_format="json"
return_array "${X[#]}"
echo "JSON for JS"
return_format=
return_array "${X[#]}"
will produce output:
PHP 5.x
<?php return array("hello world", "goodnight moon", "say \"boo\"", "foo\\bar");
PHP 7.x
<?php return ["hello world", "goodnight moon", "say \"boo\"", "foo\\bar"];
JSON for PHP
{"hello world", "goodnight moon", "say \"boo\"", "foo\\bar"}
JSON for JS
["hello world", "goodnight moon", "say \"boo\"", "foo\\bar"]

Related

jq pass an array that should be converted into json array

I have script
#!/bin/bash
ARR=("a" "b")
collection_init_msg=$( jq -n --arg arr $ARR '{arr: [$arr]}')
echo some command "$collection_init_msg"
that should convert the ARR and print it as a JSON array.
Current result
some command {
"arr": [
"a"
]
}
What I want is:
some command {
"arr": [
"a", "b"
]
}
#!/bin/bash
ARR=("a" "b")
collection_init_msg=$( jq -nc '{arr: $ARGS.positional}' --args "${ARR[#]}" )
echo "$collection_init_msg"
In answer to the supplementary question in the comment: one could do worse than:
jq -n --argjson a1 $(jq -nc '$ARGS.positional' --args "${A1[#]}") '
{$a1, a2: $ARGS.positional}' --args "${A2[#]}"

Appending to an array in bash, why this isn't working?

I'm trying from a list of files with a pattern correctly matched by regex to check whether this value is in my array, if not, append it.
Unfortunately, this code that I build up inspired by some stack overflow post doesn't work (nothing is happened, the =~ doesn't seem to find the bash_rematch, and also it doesn't output anything?
sample_array=() #creating the array
for context_files in data/*.txt.gz # checking all the different samples id we have
do
[[ $context_files =~ SL[0-9]{6} ]]
echo 'context file:' "$context_files"
echo 'rematch:' "${BASH_REMATCH[0]}"
if ! [[ " ${sample_array[*]} " =~ (^|[[:space:]])"${BASH_REMATCH[0]}"($|[[:space:]]) ]]; then
echo 'condition matched'
echo 'rematch:' "${BASH_REMATCH[0]}"
sample_array+=(" ${BASH_REMATCH[0]} ")
fi
done
echo "${sample_array[*]}"
replacing this code by
sample_array=() #creating the array
for context_files in data/*.txt.gz # checking all the different samples id we have
do
[[ $context_files =~ SL[0-9]{6} ]]
echo 'context file:' "$context_files"
echo 'rematch:' "${BASH_REMATCH[0]}"
if ! [[ " ${sample_array[*]} " == "${BASH_REMATCH[0]}" ]]; then
echo 'condition matched'
echo 'rematch:' "${BASH_REMATCH[0]}"
sample_array+=(" ${BASH_REMATCH[0]} ")
fi
done
echo "${sample_array[*]}"
will this time add all the variable
output :
A B A B A B
I probably don't get something in how the if is managed and/or how the regex lookup in a bash array is to be made but I'd gladly get some help!
The second match is negated, so in order to enter the then part, the match needs to fail. A failed match resets $BASH_REMATCH.
#! /bin/bash
sample_array=()
for context_files in data/SL{111111,222222,333333,111111,222222}.txt.gz ; do
[[ $context_files =~ SL[0-9]{6} ]]
match=${BASH_REMATCH[0]}
echo 'context file:' "$context_files"
echo 'rematch:' "$match"
if ! [[ " ${sample_array[*]} " =~ (^|[[:space:]])"$match"($|[[:space:]]) ]]; then
echo 'condition matched'
echo 'rematch:' "$match"
sample_array+=(" $match ")
fi
done
echo "${sample_array[*]}"
Here is a completely alternative solution in bash-style like John Kugelman suggested:
printf %s\\n data/*.txt.gz | grep -Eo 'SL[0-9]{6}' | sort -u
If you need the results in an array, use mapfile:
mapfile -t array <(printf %s\\n data/*.txt.gz | grep -Eo 'SL[0-9]{6}' | sort -u)

Bash, compare string to arrayvalue

i try to match a parameter with some array content. At the if clauses should be true, but it wont be.
At the output before compare i got this:
VAL: drei_01 AND: drei
#!/bin/bash
array=( null_01 eins_01 zwei_01 drei_01 vier_01 )
lookarr() {
maxc=${#array[#]}
mbool=0
for((i=0; i<$maxc; i++))
do
val=${array[$i]}
echo "VAL: $val AND: $1"
if [[ $1 == *" $val "* ]]; then
echo "TESTENTRY1"
#do something
mbool=1
break
fi
done
if [[ $mbool -eq 0 ]]; then
echo "TESTENTRY2"
#do something else
fi
}
lookarr drei
thanks
Your if statement isn't matching because it is back-to-front and has extra spaces. For drei to match drei_01 you can replace your if statement with:
if [[ "$val" == *"$1"* ]]; then

Translate Array with Array

I search a way to translate a key from one array with other array.
tmp_title=$3
title=$(echo ${tmp_title,,} | sed -e 's/\s/-/g')
tags=(computer media state society)
de=(computer medien staat gesellschaft)
fr=(ordinateur journalisme politique société)
ru=(Компьютер СМИ штат общество)
file="./content/de/blog/$(date +"%Y")/$(date +"%m")/$title.md"
if test -n "$2"; then
# check tag is in tags array
if [[ ${tags[*]} =~ $2 ]]; then
# check the folder structure is right
if [[ -d ./content/de/blog/$(date +"%Y")/$(date +"%m") ]]; then
# create the content and fill up the file
echo "---" >> "$file"
echo "title: \"$3\"" >> "$file"
echo "date: $(date +\"%Y-%m-%d\")" >> "$file"
echo "draft: false" >> "$file"
echo "tags: \"$2\"" >> "$file"
echo "shorttext:" >> "$file"
echo "cover: \"$2\"" >> "$file"
echo "lang: $1" >> "$file"
echo "---" >> "$file"
fi
else
echo "Enter a valid tag name ..."
fi
fi
I search a way to translate "tags: \"$2\"" >> "$file" in the language array value. When I append society to the script then should be tags: "gesellschaft".
Thank you for help.
Silvio
Consider this approach
case $LANG in
*en*) tags=(computer media state society );;
*de*) tags=(computer medien staat gesellschaft);;
*fr*) tags=(ordinateur journalisme politique société );;
*ru*) tags=(Компьютер СМИ штат общество );;
esac
I have it realize other. I ask for categories and translate then to the $lang value.
#!/usr/bin/env bash
# variables through user input
lang=$1
cover=$2
tmp_title=$3
# variables which we use in script
# create a title with small letters and remove whitespace
title=$(echo ${tmp_title,,} | sed -e 's/\s/-/g')
# categories
categories=(computer media repression society)
# date variables
date=$(date +"%Y-%m-%d")
year=$(date +"%Y")
month=$(date +"%m")
# content variables
content_dir="./content/$lang/blog/$year/$month"
file="$content_dir/$title.md"
# function
function create_file()
{
{
echo "---"
echo "title: \"$tmp_title\""
echo "date: $date"
echo "draft: false"
echo "tags: \"$tag\""
echo "shorttext:"
echo "cover: \"$cover\""
echo "lang: $lang"
echo "---"
} >> "$file"
}
case $1 in
de)
if test -n "$lang"; then
# check tag is in array
if [[ ${categories[*]} =~ $cover ]]; then
# translation tag > categories
if [[ $cover =~ "computer" ]]; then
tag="Computer"
elif [[ $cover =~ "media" ]]; then
tag="Medien"
elif [[ $cover =~ "repression" ]]; then
tag="Staat"
elif [[ $cover =~ "society" ]]; then
tag="Gesellschaft"
fi
# check the folder structure is right
if [[ -d "$content_dir" ]]; then
# create the content and fill up the file
create_file
if [[ -f "$file" ]]; then
subl "$file"
fi
else
# create the folder of content
mkdir -p "$content_dir"
# create the content and fill up the file
create_file
if [[ -f "$file" ]]; then
subl "$file"
fi
fi
else
echo "Enter a valid tag name ..."
fi
fi
;;
en)
if test -n "$lang"; then
# check tag is in array
if [[ ${categories[*]} =~ $cover ]]; then
# translation tag > categories
if [[ $cover =~ "computer" ]]; then
tag="Computer"
elif [[ $cover =~ "media" ]]; then
tag="Media"
elif [[ $cover =~ "repression" ]]; then
tag="State"
elif [[ $cover =~ "society" ]]; then
tag="Society"
fi
# check the folder structure is right
if [[ -d "$content_dir" ]]; then
# create the content and fill up the file
create_file
if [[ -f "$file" ]]; then
subl "$file"
fi
else
# create the folder of content
mkdir -p "$content_dir"
# create the content and fill up the file
create_file
if [[ -f "$file" ]]; then
subl "$file"
fi
fi
else
echo "Enter a valid tag name ..."
fi
fi
;;
info)
echo "To work with this script you need append the follow stuff"
echo "./bin/new.sh lang cover title"
echo "./bin/news.sh en society 'This is a title'"
echo "cover: computer, media, repression, society"
;;
esac
Silvio

Check multiple var if exists in array with grep

I am using this code to check one $var if exists in array :
if echo ${myArr[#]} | grep -qw $myVar; then echo "Var exists on array" fi
How could I combine more than one $vars to my check? Something like grep -qw $var1,$var2; then ... fi
Thank you in Advance.
if echo ${myArr[#]} | grep -qw -e "$myVar" -e "$otherVar"
then
echo "Var exists on array"
fi
From the man-page:
-e PATTERN, --regexp=PATTERN
Use PATTERN as the pattern.
This can be used to specify multiple search patterns, or to protect a pattern beginning with a hyphen (-). (-e is specified by POSIX.)
But if you want to use arrays like this you might as well use the bash built-in associative arrays.
To implement and logic:
myVar1=home1
myVar2=home2
myArr[0]=home1
myArr[1]=home2
if echo ${myArr[#]} | grep -qw -e "$myVar1.*$myVar2" -e "$myVar2.*$myVar1"
then
echo "Var exists on array"
fi
# using associative arrays
declare -A assoc
assoc[home1]=1
assoc[home2]=1
if [[ ${assoc[$myVar1]} && ${assoc[$myVar2]} ]]; then
echo "Var exists on array"
fi
Actually you don't need grep for this, Bash is perfectly capable of doing Extended Regular Expressions itself (Bash 3.0 or later).
pattern="$var1|$var2|$var3"
for element in "${myArr[#]}"
do
if [[ $element =~ $pattern ]]
then
echo "$pattern exists in array"
break
fi
done
Something quadratic, but aware of spaces:
myArr=(aa "bb c" ddd)
has_values(){
for e in "${myArr[#]}" ; do
for f ; do
if [ "$e" = "$f" ]; then return 0 ; fi
done
done
return 1
}
if has_values "ee" "bb c" ; then echo yes ; else echo "no" ; fi
this example will print no because "bb c" != "bb c"

Resources