How to update a element in array by index with jq? - jq

With this example:
[111, 222, 333, 444]
I want to change the 3rd element (or other from bash variable).

You can use the following jq command :
jq --argjson index "$index" '.[$index] |= <new value>'
|= is the update operator, it lets you change the value of what is addressed on its left hand operand without changing the path, so that you can return the whole array rather than just the updated value.
Note that indices are 0-based, so to change the 3rd element you want to set the bash variable $index to 2.
Using --argjson instead of the usual --arg is needed to avoid handling the variable as a string but rather as a number which we can use as an index.
You can (somewhat) try it here.

Related

filename expansion on assigning a non-array variable

This is about Zsh 5.5.1.
Say I have a glob pattern which expands to exactly one file, and I would like to assign this file to a variable. This works:
# N: No error if no files match. D: Match dot files. Y1: Expand to exactly one entry.
myfile=(*(NDY1))
and echo $myfile will show the file (or directory). But this one does not work:
myfile=*(NDY1)
In the latter case, echo $myfile holds the pattern, i.e. *(NDY1).
Of course I could do some cheap trick, such as creating a chilprocess via
myfile=$(echo *(NDY1))
but is there a way to do the assinment without such tricks?
By default, zsh does not do filename expansion in scalar assignment, but the option GLOB_ASSIGN could help. (This option is provided as for backwards compatibility only.)
local myfile=''
() {
setopt localoptions globassign
myfile=*(NDY1)
}
echo $myfile
;#>> something
Here are some descriptions in zsh docs:
The value of a scalar parameter may also be assigned by writing:
name=value
In scalar assignment, value is expanded as a single string, in which the elements of arrays are joined together; filename expansion is not performed unless the option GLOB_ASSIGN is set.
--- zshparam(1), Description, zsh parameters
GLOB_ASSIGN <C>
If this option is set, filename generation (globbing) is performed on the right hand side of scalar parameter assignments of the form 'name=pattern (e.g. foo=*'). If the result has more than one word the parameter will become an array with those words as arguments. This option is provided for backwards compatibility only: globbing is always performed on the right hand side of array assignments of the form name=(value) (e.g. foo=(*)) and this form is recommended for clarity; with this option set, it is not possible to predict whether the result will be an array or a scalar.
--- zshoptions(1), GLOB_ASSIGN, Expansion and Globbing, Description Of Options, zsh options

Generate an associative array from command output

I am writing a zsh completion function to complete IDs from a database. There is a program listnotes which outputs a list like this:
bf848bf6-63d2-474b-a2c0-e7e3c4865ce8 Note Title
aba21e55-22c6-4c50-8bf6-bf3b337468e2 Another one
09ead915-bf2d-449d-a943-ff589e79794a yet another "one"
...
How do I generate an associative array note_ids from the output of the listnotes command such that I get an associative array like this?
( bf848bf6-63d2-474b-a2c0-e7e3c4865ce8 "Note Title" aba21e55-22c6-4c50-8bf6-bf3b337468e2 "Another one" 09ead915-bf2d-449d-a943-ff589e79794a "yet another \"one\"" )
Note that there may be whitespace in the keys. I tried to generate something with sed:
note_ids=($(listnotes | sed 's/^\(.*\) \(.*\)$/\1 "\2"/'))
but quoting strings like this doesn’t seem to work, and double quotes in the title make it even more difficult.
Try something like
typeset -A note_ids
for line in ${(f)"$(listnotes)"}; do
note_ids+=(${line%% *} ${line#* })
done
${(f)PARAM}: split the result of the expansion of $PARAM at newlines
"$(listnotes)": put the output of listnotes verbatim into the expansion.
for line in LIST: iterate over the items in LIST as split by ${(f)…}.
note_ids+=(key value): add key-value pair to an the associative array note_ids
${line%% *}: cut the largest portion matching " *" (a space followed by anything) from the end of the expansion of line. So remove everying after including the first space, leaving only the key.
${line#* }: cut the smallest portion matching "* " (anything followed by three spaces) from the beginning of the expansion of $line. So remove the key and the three spaces used as separator.
Instead of using the parameter expansion flag (f) you could also read the output of listnotes line by line with read:
listnotes | while read; do
note_ids+=(${REPLY%% *} ${REPLY#* })
done
Unless specified otherwise read puts the read values into the REPLY parameter.

Filtering zsh array by wildcard

Given a Zsh array myarray, I can make out of it a subset array
set -A subarray
for el in $myarray
do
if [[ $el =~ *x*y* ]]
then
subarray+=($el)
fi
done
which, in this example, contains all elements from myarray which have somewhere an x and an y, in that order.
Question:
Given the plethora of array operations available in zsh, is there an easier way to achieve this? I checked the man page and the zsh-lovers page, but couldn't find anything suitable.
This should do the trick
subarray=(${(M)myarray:#*x*y*z})
You can find the explanation in the [section about Parameter Expansion] in the zsh manpage. It is a bit hidden as ${name:#pattern} without the flag (M) does the opposite of what you want:
${name:#pattern}
If the pattern matches the value of name, then substitute the empty string; otherwise, just substitute the value of name. If name is an array the matching array elements are removed (use the (M) flag to remove the non-matched elements).

Replacing a specific part

I have a list like this:
DEL075MD1BWP30P140LVT
AN2D4BWP30P140LVT
INVD0P7BWP40P140
IND2D6BWP30P140LVT
I want to replace everything in between D and BWP with a *
How can I do that in unix and tcl
Do you have the whole list available at the same time, or are you getting one item at a time from somewhere?
Should all D-BWP groups be processed, or just one per item?
If just one per item, should it be the first or last (those are the easiest alternatives)?
Tcl REs don't have any lookbehind, which would have been nice here. But you can do without both lookbehinds and lookaheads if you capture the goalpost and paste them into the replacement as back references. The regular expression for the text between the goalposts should be [^DB]+, i.e. one or more of any text that doesn't include D or B (to make sure the match doesn't escape the goalposts and stick to other Ds or Bs in the text). So: {(D)[^DB]+(BWP)} (braces around the RE is usually a good idea).
If you have the whole list and want to process all groups, try this:
set result [regsub -all {(D)[^DB]+(BWP)} $lines {\1*\2}]
(If you can only work with one line at a time, it's basically the same, you just use a variable for a single line instead of a variable for the whole list. In the following examples, I use lmap to generate individual lines, which means I need to have the whole list anyway; this is just an example.)
Process just the first group in each line:
set result [lmap line $lines {
regsub {(D)[^DB]+(BWP)} $line {\1*\2}
}]
Process just the last group in each line:
set result [lmap line $lines {
regsub {(D)[^DB]+(BWP[^D]*)$} $line {\1*\2}
}]
The {(D)[^DB]+(BWP[^D]*)$} RE extends the right goalpost to ensure that there is no D (and hence possibly a new group) anywhere between the goalpost and the end of the string.
Documentation:
lmap (for Tcl 8.5),
lmap,
regsub,
set,
Syntax of Tcl regular expressions

How to grep value from a line in Unix

Suppose I have a lines as follows:
<Instance name="cd" id="sa1">
<work id="23" permission="r">
I want to get the id value printed, where the id field is not constant.
It hard to give a hint without doing it for you. But assuming your real needs are more involved than you describe, then perhaps some learning can happen while applying this answer.
Grep isn't really powerful enough to do the job you describe, although it may be useful in a pipline to select data at a larger "grain". If your file has one-tag-per-line like your example shows, you can use grep to filter just the Instance or work tags.
grep Instance | program to extract id val
or
grep work| program to extract id val
To extract the value you need something more powerful than grep. Assuming the value is enclosed in double-quotes and contains no embedded quotes; and that there are no similarly named attributes that could confuse the expression, this sed magic should do the trick.
sed 's/.*id="\([^"]*\)".*/\1/'
If any one the above asumptions are not true, the expression will have to be more complicated.

Resources