jq icon indicating copy to clipboard operation
jq copied to clipboard

Make @sh work with objects; add @shassoc (fix #1947)

Open nicowilliams opened this issue 2 years ago • 11 comments
trafficstars

  • mention eval in @sh docs
  • make @sh work on objects, emitting shell variable assignments for ident-like keys
  • add @shassoc that emits text that can be used with declare -A x; eval x=($(jq -r ...))

nicowilliams avatar Aug 06 '23 22:08 nicowilliams

The examples are wrong; they are using unquoted expansions that will split the output by whitespace, and then eval will join the splits back collapsing a sequence of multiple spaces into a single space. also globs will be expanded.

declare -A x; eval x=\($(jq -r '@shassoc' <<< '{"foo   bar": "x  /*   y"}')\)

$(jq -r '@shassoc' <<< '{"foo bar": "x /* y"}') will expand into to ['foo bar']='x /* y' since it is unquoted, it will be split into:

<x=(['foo> <bar']='x> </*> <y')>

And again, since it is unquoted, /* will expand, ending up running:

eval 'x=(['\''foo' 'bar'\'']='\''x' '/bin' '/etc' '/...' 'y'\'')'

That will eval x=(['foo bar']='x /bin /etc /... y'), which is wrong: all spaces collapsed into one, and /* is randomly expanded. Also /* may expand to something that injects syntax and runs a command.

Even for simple cases, [foo] is a glob pattern that matches either f or o, using an unquoted expansion will make it expand.

Anyway, sorry, but even if you fix the problem, I don't really like this implementation at all. And I don't think even think this is useful, or that we should add it.

Do you really want to add this feature?

emanuele6 avatar Aug 07 '23 00:08 emanuele6

: ; declare -A x
: ; eval x=($(./jq -nr '{"a b":"*"}|@shassoc'))
: ; echo ${#x[@]} "${x[@]}"
1 *
: ; echo ${#x[@]} "${!x[@]}" "${x[@]}"
1 a b *
: ; eval x=($(./jq -nr '{"*":"*"}|@shassoc'))
: ; echo ${#x[@]} "${!x[@]}" "${x[@]}"
1 * *
: ; 

nicowilliams avatar Aug 07 '23 00:08 nicowilliams

@nicowilliams

It only makes sense that x=(['*']='*') doesn't glob expand to the files in your directory, the are no spaces so it is a glob in its entirety. It would expand to files named x=(*='foo') or x='=('foo bar' baz') in your current directory if there were any (and it would expand to nothing if nullglob was in use, like ['foo']='bar' also would since [] is a glob component too). And "a b" does not have sequence of multiple spaces so you don't notice that the sequences of spaces are collapsed into a single space by eval joining them back after splitting.

$ (declare -A x; eval x=($(./jq -nr '{"*":"*"}|@shassoc')); declare -p x)
declare -A x=(["*"]="*" )
$ touch "x=(*='foo' bar')" "x=('='x')"
$ (declare -A x; eval x=($(./jq -nr '{"*":"*"}|@shassoc')); declare -p x)
declare -A x=(["*=foo"]="bar) x=(=x" )
$ (declare -A x; eval "x=($(./jq -nr '{"*":"*"}|@shassoc'))"; declare -p x) #quoted
declare -A x=(["*"]="*" )

You can also check my example:

$ (declare -A x; eval x=\($(./jq -r '@shassoc' <<< '{"foo   bar": "x  /*   y"}')\); declare -p x)
declare -A x=(["foo bar"]="x /bin /boot /dev /etc /home /lib /lib64 /lost+found /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var y" )
$ (declare -A x; eval "x=($(./jq -r '@shassoc' <<< '{"foo   bar": "x  /*   y"}'))"; declare -p x) #quoted
declare -A x=(["foo   bar"]="x  /*   y" )

emanuele6 avatar Aug 07 '23 00:08 emanuele6

Oh, I see. Maybe then @shassoc should expand to var[index]=value with the index and value escaped? but whence the associative array variable name? From an empty key's value?

nicowilliams avatar Aug 07 '23 02:08 nicowilliams

You just need to double quote the expansion so it is not splitted and glob expanded: eval "x=($(jq ...))" instead of eval x=($(jq ...)); also note that you don't need to use eval and you can use declare directly in bash: declare -A "x=($(jq ...))".

Anyway, I am still of the opinion that this is not a good feature to add. And also note that bash (but not ksh93) disallows the use of '' as key for an associative array, and you are not handling that case which will result in an assignment error.

emanuele6 avatar Aug 07 '23 02:08 emanuele6

Anyway, I am still of the opinion that this is not a good feature to add. And also note that bash (but not ksh93) disallows the use of '' as key for an associative array, and you are not handling that case which will result in an assignment error.

Fair enough. I'll remove it.

nicowilliams avatar Aug 07 '23 02:08 nicowilliams

Can you write `@sh` instead of @sh in your commit message so github does not render it as a user mention ^^

emanuele6 avatar Aug 07 '23 03:08 emanuele6

I recently made a jq module to do some of this, including the functionality that was removed. With the goal of mapping objects to identifiers, sh::param has an optional prefix argument (default is _), so you don't clobber important shell parameters on accident, and can namespace multiple calls to the function.

I'm looking to find the best way to incorporate which $SHELL the user is running without turning the function into a messy 4x nested if-else tree.

Not disparaging a C builtin approach, but I just wanted to add this as option, especially if someone in the future found this PR from the issue search (like I did).

xPMo avatar Aug 20 '23 03:08 xPMo

Not disparaging a C builtin approach, but I just wanted to add this as option, especially if someone in the future found this PR from the issue search (like I did).

No disparagement taken :)

By the way, your comment prompted me to see what it would take to add user-defined formatting specifications, and... it can be done right now:

def of($fmt): format($fmt); # original `format`
def format($fmt): if $fmt == "custom" then "CUSTOM: " + . else of($fmt) end;

@custom "hi \("there")"

Now, modules can't quite do this unless they're included instead of imported, but one could always document that the user of a module that defines formats can def format($fmt): the_module::format($fmt); to use them.

nicowilliams avatar Aug 21 '23 16:08 nicowilliams

By the way, your comment prompted me to see what it would take to add user-defined formatting specifications, and... it can be done right now:

There is jaq PR https://github.com/01mf02/jaq/pull/105 discussion add formatters and custom formatters by allowing a function to start with "@" which seems quite nice.

wader avatar Aug 21 '23 17:08 wader

There is jaq PR 01mf02/jaq#105 discussion add formatters and custom formatters by allowing a function to start with "@" which seems quite nice.

I like that, yeah.

nicowilliams avatar Aug 21 '23 17:08 nicowilliams