英文:
How to remove trailing commas from array in JSON
问题
I can help with the translation:
json=$(printf '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$(printf '"%s",' "${imgArray[@]%,}")" "$(printf '"%s",' "${typeArray[@]}")")
这是格式化JSON的printf
语句。在输出中,末尾的逗号是由%s,
语句在printf
中引起的。
要修改printf
语句以不在数组的最后一项使用逗号,您可以使用sed
等命令来处理JSON字符串以去除末尾逗号。
英文:
I am trying make a bash script to get user input, format that input into a JSON, then post that JSON to a Firebase Realtime Database using the REST API. After getting the input, I use printf
statements to format the input into a JSON. The issue is that I have a trailing comma after the last entry in an array.
Here is the printf
statement to format the JSON
json=$(printf '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$(printf '"%s",' "${imgArray[@]%,}")" "$(printf '"%s",' "${typeArray[@]}")")
Here is a sample output
{"itemName":"itemname","images":["url1","url2","url3",],"type":["all","hats",],"link":"/"}
I understand the training comma comes from the %s,
statement in the printf
.
Is there a way to modify the printf
statement to not use a comma at the last entry in the array? Or is there a way to use commands like sed
to remove that trailing comma?
答案1
得分: 1
Assumptions:
- the 1st element in both arrays have index
0
Sample inputs:
itemName='itemname'
imgArray=(url1 url2 url3)
typeArray=(all hats)
NOTE: While there may be a way to do this with a json tool (eg, jq
), I'll focus on some general bash
capabilities ...
The general approach is to print the 1st element by itself, then preface the 2nd-nth elements with a comma.
Breaking the current printf
calls into two parts:
# these:
$(printf '"%s",' "${imgArray[@]%,}")
$(printf '"%s",' "${typeArray[@]}")
# becomes these:
$(printf '"%s"' "${imgArray[0]}" ; printf ',"%s"' "${imgArray[@]:1}")
$(printf '"%s"' "${typeArray[0]}"; printf ',"%s"' "${typeArray[@]:1}")
This (still) requires OP's code to make 3 subprocess calls.
One idea to eliminate the subprocesses consists of using the (bash
) printf
builtin to populate some variables (imgs
, types
, json
):
printf -v imgs ',"%s"' "${imgArray[@]:1}" # preface 2nd-nth elements with a comma
imgs="'${imgArray[0]}'${imgs}" # add 1st element
printf -v types ',"%s"' "${typeArray[@]:1}" # preface 2nd-nth elements with a comma
types="'${typeArray[0]}'${types}" # add 1st element
printf -v json '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$imgs" "$types"
This generates:
$ typeset -p json
declare -- json="{"itemName":"itemname","images":["url1","url2","url3"],"type":["all","hats"],"link":"/"}"
$ echo "$json"
{"itemName":"itemname","images":["url1","url2","url3"],"type":["all","hats"],"link":"/"}
英文:
Assumptions:
- the 1st element in both arrays have index
0
Sample inputs:
itemName='itemname'
imgArray=(url1 url2 url3)
typeArray=(all hats)
NOTE: While there may be a way to do this with a json tool (eg, jq
), I'll focus on some general bash
capabilities ...
The general approach is to print the 1st element by itself, then preface the 2nd-nth elements with a comma.
Breaking the current printf
calls into two parts:
# these:
$(printf '"%s",' "${imgArray[@]%,}")
$(printf '"%s",' "${typeArray[@]}")
# becomes these:
$(printf '"%s"' "${imgArray[0]}" ; printf ',"%s"' "${imgArray[@]:1}")
$(printf '"%s"' "${typeArray[0]}"; printf ',"%s"' "${typeArray[@]:1}")
This (still) requires OP's code to make 3 subprocess calls.
One idea to eliminate the subprocesses consists of using the (bash
) printf
builtin to populate some variables (imgs
, types
, json
):
printf -v imgs ',"%s"' "${imgArray[@]:1}" # preface 2nd-nth elements with a comma
imgs="\"${imgArray[0]}\"${imgs}" # add 1st element
printf -v types ',"%s"' "${typeArray[@]:1}" # preface 2nd-nth elements with a comma
types="\"${typeArray[0]}\"${types}" # add 1st element
printf -v json '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$imgs" "$types"
This generates:
$ typeset -p json
declare -- json="{\"itemName\":\"itemname\",\"images\":[\"url1\",\"url2\",\"url3\"],\"type\":[\"all\",\"hats\"],\"link\":\"/\"}"
$ echo "$json"
{"itemName":"itemname","images":["url1","url2","url3"],"type":["all","hats"],"link":"/"}
答案2
得分: 1
安装 jq
并使用它生成 JSON 文件。Bash 和 sed
不是用于操作 JSON 的正确工具。
在您的情况下,构建 JSON 不能在单一步骤中完成。让我们从图像列表开始:
for i in "${ITEMS[@]}"; do
echo "$i"
done |
jq -sRc '. | rtrimstr("\n") | split("\n")'
上面的 for
命令将 $ITEMS
Bash 数组变量的项目逐行列出。输出通过管道传递给 jq
,并按以下方式进行处理:
- 命令行选项
-s
告诉它将所有输入数据一次性吸入数组,而不是单独处理每行; - 选项
-R
表示“原始输入”;与s
结合使用,它告诉jq
读取整个输入为一个大字符串,并仅对其应用程序一次; - 选项
-c
告诉它生成紧凑的输出(整个 JSON 放在一行上,没有额外的空格)。
短选项可以组合成单个单词。-sRc
与 -s -R -c
相同。
jq
程序:
. # “.” 总是表示“当前项目”;这意味着这里的输入字符串
| # 将上一个筛选器生成的值传递给下一个筛选器
rtrimstr("\n") # 去除当前项目右侧的“\n”
| # 将(已修剪的字符串)传递给下一个项目
split("\n") # 使用换行符作为分隔符拆分输入字符串
jq
程序基本上执行了与 for
循环相反的操作,但非常重要的是,它处理原始数据并生成 JSON(字符串数组)。
假设数组初始化如下:
ITEMS=(foo bar "baz boo")
... 上面的脚本会生成:
["foo","bar","baz boo"]
这使我们能够编写一个更大的命令行,以执行您所需的操作:
jq -n \
--arg name "$itemName" \
--argjson images "$(for img in "${imgArray[@]}"; do echo $img; done | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
--argjson types "$(for type in "${typeArray[@]}"; do echo $type; done | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
'{"itemName": $name, "images": $images, "type": $types, "link": "/"}'
命令行选项:
-n
告诉jq
不要从stdin
读取任何内容;--arg
定义一个变量(name
),其值是原始字符串,下一个参数);Bash 通过展开"$itemName"
计算该参数;--argjson
定义一个变量(images
),其值为 JSON;jq
在执行程序之前解析此 JSON;“$(...)”产生括号之间命令的评估结果,结果是一个单词;该命令如上所述,从 Bash 数组变量生成一个编码为 JSON 的数组;{"itemName": $name, "images": $images, "type": $types, "link": "/}
是jq
程序;它生成一个对象,其键为itemName
、image
、type
和link
,其值是使用--arg
和--argjson
设置的jq
变量的值;link
的值是字符串文字/
。
总的来说,这个调用 jq
三次的大命令行会执行您需要的操作,您无需担心尾随逗号或其他 JSON 细节。您只需正确地将所有内容包装在引号中。
在线检查它 here。
更新
因为将 imgArray
渲染成编码为字符串数组的 JSON 的代码在子 shell 中运行,我们可以更改 IFS,并使用单个 echo
代替 for
循环以更快地获得相同的结果:
IFS=$'\n'; echo "${imgArray[*]}"
完整脚本变为:
jq -n \
--arg name "$itemName" \
--argjson images "$(IFS=$'\n'; echo "${imgArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
--argjson types "$(IFS=$'\n'; echo "${typeArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
'{"itemName": $name, "images": $images, "type": $types, "link": "/"}'
它具有相同的输出,但更短,应该运行得更快。在线检查它 [here](https://tio.run/##nU89b8IwFNz9K05WJCcoARW2oAwsSF060LHpYJCVGGwDthk
英文:
Install jq
and use it to generate JSON files. Bash and sed
are not the right tools to manipulate JSON.
In your case, building the JSON cannot be done in a single step. Let's start with the list of images:
for i in "${ITEMS[@]}"; do
echo "$i"
done |
jq -sRc '. | rtrimstr("\n") | split("\n")'
The for
command above lists the items of the $ITEMS
Bash array variable one per line. The output is piped to jq
that processes it as follows:
- the command line option
-s
tells it to "slurp" all the input data into an array instead of processing each line individually; - the option
-R
means "raw input"; combined withs
it tellsjq
to read the whole input in a big string and apply the program only once to it; - the option
-c
tells it to produce compact output (the entire JSON on a single line, without extra spaces).
The short options can be combined into a single word. -sRc
is the same as -s -R -c
.
The jq
program:
. # `.` always means "the current item"; this means the input string here
| # pipe the value produced by the previous filter to the next filter
rtrimstr("\n") # trim "\n" on the right side of the current item
| # pass (the trimmed string) to the next item
split("\n") # split the input string using newlines as the separator
The jq
program basically does the opposite of the for
loop but, very important, it processes raw data and produces a JSON (an array of strings).
Assuming the array is initialized as:
ITEMS=(foo bar "baz boo")
... the script above produces:
["foo","bar","baz boo"]
This allows us to write a larger command line that does what you need:
jq -n \
--arg name "$itemName" \
--argjson images "$(for img in "${imgArray[@]}"; do echo $img; done | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
--argjson types "$(for type in "${typeArray[@]}"; do echo $type; done | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
'{ itemName: $name, images: $images, type: $types, link: "/" }'
The command line options:
-n
tellsjq
to not read anything fromstdin
;--arg
defines a variable (name
) whose value is a raw string, the next argument); Bash computes that argument by expanding"$itemName"
;--argjson
defines a variable (images
) whose value is a JSON;jq
parses this JSON before starting the execution of the program; "$(...)" produces the evaluation of the command between parentheses and the result is a single word; the command is the one described above, that produces an array encoded as JSON from a Bash array variable;{ itemName: $name, images: $images, type: $types, link: "/" }
is thejq
program; it produces an object having the keysitemName
,image
,type
andlink
, whose values are produced from the values of thejq
variables set using--arg
and--argjson
; the value oflink
is the string literal/
.
All in all, this big command line that invokes jq
three times does what you need and you don't have to worry about trailing commas or other JSON details. All you need to care is about wrapping everything in quotes properly.
Check it online.
Update
Because the code that renders imgArray
into a JSON that encodes an array of strings runs in a sub-shell, we can change the IFS and use a single echo
instead of a for
loop to get the same outcome faster:
IFS=$'\n'; echo "${imgArray[*]}"
The complete script becomes:
jq -n \
--arg name "$itemName" \
--argjson images "$(IFS=$'\n'; echo "${imgArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
--argjson types "$(IFS=$'\n'; echo "${typeArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
'{ itemName: $name, images: $images, type: $types, link: "/" }'
It has the same output but it's shorter and it should run faster. Check it oneline.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论