0

Here's my script:

if [[ $(jq '.haystack | index("needle")' /etc/xyz/daemon.json) = \0 ]] ; then
    jq '.haystack += ["needle"]' /etc/xyz/daemon.json > daemon.json
    mv -f daemon.json /etc/xyz/daemon.json
fi

I want to add needle to haystack array in a daemon.json file. However the problem is in the if construct of the shell. When I test the condition with echo True/False the terminal does show True or False based on the command in the condition. However, I cannot do any other command, such as a simple ls or mkdir. I execute the command from terminal, not saving into a bash file. Newline doesn't seem to need \.

I'm completely new to Linux terminal, is there something I'm missing here? Thanks!

Minh Nghĩa
  • 854
  • 1
  • 11
  • 28
  • Can you provide a [mcve], with complete and runnable code? We won't have a copy of `daemon.json`. – Charles Duffy Aug 26 '19 at 19:35
  • 1
    A place it might start is to include the logs from `bash -x yourscript` [edit]ed into the question text. – Charles Duffy Aug 26 '19 at 19:36
  • BTW, what directory is this run in? If it's run in `/etc/xyz`, the reasons it fails should be fairly obvious. – Charles Duffy Aug 26 '19 at 19:39
  • Also, there's no reason to use `\0` rather than just `0`. (The shell's behavior on seeing a backslash is not well-specified by POSIX; it may either discard it or treat it as literal, as an implementation decision). – Charles Duffy Aug 26 '19 at 19:43
  • 2
    If you only want to add `"needle"` if it isn't already there, you would need to check if `index` returns `null`, not 0 (which indicates that "needle" is the first element in the list). – chepner Aug 26 '19 at 19:53

1 Answers1

2

Moving your input to a function to allow testing, and replacing \0 (which has undefined behavior) with 0 (which always expands precisely to itself when given as a literal in code):

get_current_json() {
  printf '%s\n' '{"haystack": ["needle", "other1", "other2"]}'
}

if [[ $(jq '.haystack | index("needle")' < <(get_current_json) ) = 0 ]] ; then
  echo "Found (at position 0)"
else
  echo "Not found (at position 0)"
fi

...correctly emits Found. Thus, the issue cannot be reproduced given only the code provided in the question, without further context.


One potential piece of context: If you're running this in /etc/xyz, then > daemon.json is opening the file for output with the O_TRUNC flag, and thus emptying its contents, before jq begins execution (which can happen only after its output file descriptor is connected), and thus before jq is able to read any prior values which the open(..., O_TRUNC) would delete.

To avoid this, you should use a unique name for your temporary files, as created by mktemp. (Ideally, those names should be in the same directory as the destination file, to guarantee that they don't cross filesystem boundaries and that the final mv will be atomic). See How can I use a file in a command and redirect output to the same file without truncating it?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 1
    You could use `-e / --exit-status`: `if jq --exit-status '.haystack | index("needle")' < <(get_current_json) ) >/dev/null ; then` – Léa Gris Aug 26 '19 at 20:36
  • Good call! Always more efficient to avoid an unnecessary command substitution. Though that would differ in behavior from the OP's initial code; to match their semantics, we'd need a `== 0`, even if it's moved into the jq code. – Charles Duffy Aug 26 '19 at 20:41