英文:
How to remove trailing commas from array in JSON
问题
json=$(printf '{ "itemName":"%s", "images":[%s], "type":[%s], "link":"/" }' "$itemName" "$(printf '"%s",' "${imgArray[@]%,}")" "$(printf '"%s",' "${typeArray[@]}")")
这是 printf
语句,用于格式化 JSON。你提到的问题是由于在 printf
中的 %s,
语句导致尾随逗号。你可以使用 sed
命令来删除数组中的最后一个逗号。
英文:
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
以下是翻译好的内容:
假设:
- 两个数组的第一个元素的索引为
0
示例输入:
itemName='itemname'
imgArray=(url1 url2 url3)
typeArray=(all hats)
**注意:**虽然可能可以使用 JSON 工具(例如 jq
)来执行此操作,但我将专注于一些常规的 bash
功能...
一般的方法是将第一个元素单独打印,然后在第二个到第 n 个元素之前加上逗号。
将当前的 printf
调用分为两部分:
# 这些:
$(printf '"%s",' "${imgArray[@]%,}")
$(printf '"%s",' "${typeArray[@]}")
# 变成这些:
$(printf '"%s"' "${imgArray[0]}" ; printf ',"%s"' "${imgArray[@]:1}")
$(printf '"%s"' "${typeArray[0]}"; printf ',"%s"' "${typeArray[@]:1}")
这仍然需要执行 3 次子进程调用。
消除子进程的一个想法是使用(bash
)printf
内建命令来填充一些变量(imgs
,types
,json
):
printf -v imgs ',"%s"' "${imgArray[@]:1}" # 在第 2 到第 n 个元素之前加上逗号
imgs="\"${imgArray[0]}\"${imgs}" # 添加第一个元素
printf -v types ',"%s"' "${typeArray[@]:1}" # 在第 2 到第 n 个元素之前加上逗号
types="\"${typeArray[0]}\"${types}" # 添加第一个元素
printf -v json '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$imgs" "$types"
这将生成:
$ 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
以下是您要求的翻译的部分:
"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](https://tio.run/##nU89b8IwFNz9K05WJCcoARW2oAwsSF060LHpYJCVGGwDthkQ8NuDDQSkSl1
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论