diff --git a/docs/content/manual/manual.yml b/docs/content/manual/manual.yml index d5e625c16c..a6523fd9fa 100644 --- a/docs/content/manual/manual.yml +++ b/docs/content/manual/manual.yml @@ -2098,8 +2098,24 @@ 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))` [bash], + `typeset -A x; eval x=\($(jq -r '@shassoc' f.json)\)` [ksh93]. * `@base64`: @@ -2135,6 +2151,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="'] diff --git a/jq.1.prebuilt b/jq.1.prebuilt index 99c35dbede..b1d531242f 100644 --- a/jq.1.prebuilt +++ b/jq.1.prebuilt @@ -2290,7 +2290,19 @@ The input must be an array, and it is rendered as TSV (tab\-separated values)\. \fB@sh\fR: . .IP -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\. +The input is escaped suitable for use in a command\-line for a POSIX shell using \fBeval\fR\. 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)\. +. +.IP +E\.g\., \fBeval $(jq \-r \'@sh\' f\.json)\fR +. +.TP +\fB@shassoc\fR: +. +.IP +Like \fB@sh\fR for objects, but formats output suitable for use with \fBeval\fR in Unix shells that support Bash\-style associative arrays\. Unlike \fB@sh\fR for objects, keys that are not shell variable identifier\-like will be included\. +. +.IP +E\.g\., \fBdeclare \-A x; eval x=($(jq \-r \'@shassoc\' f\.json))\fR [bash], \fBtypeset \-A x; eval x=\e($(jq \-r \'@shassoc\' f\.json)\e)\fR [ksh93]\. . .TP \fB@base64\fR: @@ -2345,6 +2357,18 @@ jq \'@sh "echo \e(\.)"\' "O\'Hara\'s Ale" => "echo \'O\'\e\e\'\'Hara\'\e\e\'\'s Ale\'" +jq \'@sh\' + ["a b", "c d"] +=> "\'a b\' \'c d\'" + +jq \'@sh\' + {"foo":"a b", "bar":"c d", "x y":"ignored"} +=> "foo=\'a b\' bar=\'c d\'" + +jq \'@shassoc\' + {"foo":"a b", "bar":"c d", "x y":"ignored"} +=> "[foo]=\'a b\' [bar]=\'c d\'" + jq \'@base64\' "This is a message" => "VGhpcyBpcyBhIG1lc3NhZ2U=" diff --git a/src/builtin.c b/src/builtin.c index 0d0ac2340e..f8991503ee 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -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) diff --git a/tests/man.test b/tests/man.test index 822de5ab9a..46066fe9e8 100644 --- a/tests/man.test +++ b/tests/man.test @@ -706,6 +706,18 @@ bsearch(4) as $ix | if $ix < 0 then .[-(1+$ix)] = 4 else . end "O'Hara's Ale" "echo 'O'\\''Hara'\\''s Ale'" +@sh +["a b", "c d"] +"'a b' 'c d'" + +@sh +{"foo":"a b", "bar":"c d", "x y":"ignored"} +"foo='a b' bar='c d'" + +@shassoc +{"foo":"a b", "bar":"c d", "x y":"ignored"} +"[foo]='a b' [bar]='c d'" + @base64 "This is a message" "VGhpcyBpcyBhIG1lc3NhZ2U="