Skip to content

Commit

Permalink
Make @sh work with objects; add @shassoc (fix #1947)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicowilliams committed Aug 6, 2023
1 parent a692060 commit 1b34512
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 3 deletions.
31 changes: 29 additions & 2 deletions docs/content/manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2098,8 +2098,23 @@ sections:
* `@sh`:
The input is escaped suitable for use in a command-line
for a POSIX shell. If the input is an array, the output
will be a series of space-separated strings.
for a POSIX shell using `eval`. If the input is an array,
the output will be a series of space-separated strings.
If the input is an object, the output will be a series of
space-separated variable assignments for all the keys in
the object that are valid shell variable names (other keys
will be ignored).
E.g., `eval $(jq -r '@sh' f.json)`
* `@shassoc`:
Like `@sh` for objects, but formats output suitable for
use with `eval` in Unix shells that support Bash-style
associative arrays. Unlike `@sh` for objects, keys that
are not shell variable identifier-like will be included.
E.g., `declare -A x; eval x=($(jq -r '@shassoc' f.json))`.
* `@base64`:
Expand Down Expand Up @@ -2135,6 +2150,18 @@ sections:
input: "\"O'Hara's Ale\""
output: ["\"echo 'O'\\\\''Hara'\\\\''s Ale'\""]

- program: '@sh'
input: '["a b", "c d"]'
output: ["\"'a b' 'c d'\""]

- program: '@sh'
input: '{"foo":"a b", "bar":"c d", "x y":"ignored"}'
output: ["\"foo='a b' bar='c d'\""]

- program: '@shassoc'
input: '{"foo":"a b", "bar":"c d", "x y":"ignored"}'
output: ["\"[foo]='a b' [bar]='c d'\""]

- program: '@base64'
input: '"This is a message"'
output: ['"VGhpcyBpcyBhIG1lc3NhZ2U="']
Expand Down
26 changes: 25 additions & 1 deletion jq.1.prebuilt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 77 additions & 0 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,83 @@ static jv f_format(jq_state *jq, jv input, jv fmt) {
}
jv_free(input);
return line;
} else if (!strcmp(fmt_s, "shassoc")) {
jv_free(fmt);
if (jv_get_kind(input) != JV_KIND_OBJECT)
return type_error(input, "can not be escaped for shell");
jv line = jv_string("");
int first = 1;
jv_object_foreach(input, k, x) {
if (!first) line = jv_string_append_str(line, " ");
first = 0;
line = jv_string_append_str(line, "['");
line = jv_string_concat(line, escape_string(k, "''\\''\0"));
line = jv_string_append_str(line, "']=");
switch (jv_get_kind(x)) {
case JV_KIND_NULL:
case JV_KIND_TRUE:
case JV_KIND_FALSE:
case JV_KIND_NUMBER:
line = jv_string_concat(line, jv_dump_string(x, 0));
break;

case JV_KIND_STRING: {
line = jv_string_append_str(line, "'");
line = jv_string_concat(line, escape_string(x, "''\\''\0"));
line = jv_string_append_str(line, "'");
break;
}

default:
jv_free(input);
jv_free(line);
return type_error(x, "can not be escaped for shell");
}
}
jv_free(input);
return line;
} else if (!strcmp(fmt_s, "sh") && jv_get_kind(input) == JV_KIND_OBJECT) {
jv_free(fmt);
jv line = jv_string("");
int first = 1;
jv_object_foreach(input, k, x) {
if (strspn(jv_string_value(k),
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789_") != (size_t)jv_string_length_bytes(jv_copy(k)) ||
strspn(jv_string_value(k), "0123456789") != 0) {
/* Not a valid shell variable name; we don't support assignments to array variables */
jv_free(k);
jv_free(x);
continue;
}
if (!first) line = jv_string_append_str(line, " ");
first = 0;
line = jv_string_concat(line, k);
line = jv_string_append_str(line, "=");
switch (jv_get_kind(x)) {
case JV_KIND_NULL:
case JV_KIND_TRUE:
case JV_KIND_FALSE:
case JV_KIND_NUMBER:
line = jv_string_concat(line, jv_dump_string(x, 0));
break;

case JV_KIND_STRING: {
line = jv_string_append_str(line, "'");
line = jv_string_concat(line, escape_string(x, "''\\''\0"));
line = jv_string_append_str(line, "'");
break;
}

default:
jv_free(input);
jv_free(line);
return type_error(x, "can not be escaped for shell");
}
}
jv_free(input);
return line;
} else if (!strcmp(fmt_s, "sh")) {
jv_free(fmt);
if (jv_get_kind(input) != JV_KIND_ARRAY)
Expand Down
12 changes: 12 additions & 0 deletions tests/man.test

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1b34512

Please sign in to comment.