1

What I actually want to do

Save a command's output and check its return status.

The solution?

After some googling I found basically the same answer here on StackOverflow as well as on AskUbuntu and Unix/Linux StackExchange:

if output=$(command); then
  echo "success: $output"
fi

Problem

When trying out this solution with command info put the if clause is executed even if the actual command fails, but I can't explain myself why?

I tried to check the return value $? manually and it seems like the var= changes the return value:

$ info put
info: No menu item 'put' in node '(dir)Top'
$ echo $?
1

$ command info put
info: No menu item 'put' in node '(dir)Top'
$ echo $?
1

$ var=$(command info put)
info: No menu item 'put' in node '(dir)Top'
$ echo $?
0

$ var=$(command info put); echo $?
info: No menu item 'put' in node '(dir)Top'
0

It's also the same behavior when `

So why does that general solution not work in this case? And how to change/adapt the solution to make it work properly?

My environment/system

I'm working on Windows 10 with WSL2 Ubuntu 20.04.2 LTS:

$ tmux -V
tmux 3.0a

$ echo $SHELL
/bin/bash

$ /bin/bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)

$ info --version
info (GNU texinfo) 6.7
winklerrr
  • 13,026
  • 8
  • 71
  • 88

2 Answers2

2

When trying out this solution with command info put the if clause is executed even if the actual command fails, but I can't explain myself why?

Indeed, info exits with 0, when output is not a terminal and there's an error.

// texinfo/info.c
  if ((!isatty (fileno (stdout))) && (user_output_filename == NULL))
    {
      user_output_filename = xstrdup ("-");
   ...
    }
  ...
      // in called get_initial_file()
      asprintf (error, _("No menu item '%s' in node '%s'"),
        (*argv)[0], "(dir)Top");
  ...
  if (user_output_filename)
    {
      if (error)
        info_error ("%s", error);
      ...
      exit (0);                    // exits with 0!
    }

References: https://github.com/debian-tex/texinfo/blob/master/info/info.c#L848 , https://github.com/debian-tex/texinfo/blob/master/info/info.c#L277 , https://github.com/debian-tex/texinfo/blob/master/info/info.c#L1066 .

why does that general solution not work in this case?

Because the behavior of the command changes when its output is redirected not to a terminal.

how to change/adapt the solution to make it work properly?

You could simulate a tty - https://unix.stackexchange.com/questions/157458/make-program-in-a-pipe-think-it-has-tty , https://unix.stackexchange.com/questions/249723/how-to-trick-a-command-into-thinking-its-output-is-going-to-a-terminal .

You could grab stderr of the command and check if it's not-empty or match with some regex.

I think you could also contact texinfo developers and let them know that it's I think a bug and make a patch, so it would be like exit(error ? EXIT_FAILURE : EXIT_SUCCESS);.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • So it's a bug in `info`? Should we create a PR to fix that? – winklerrr Jul 02 '21 at 11:00
  • In my `bashrc` I have a function called `my_less` which checks if a command exited successfully and if so it pipes its output to `less` (basically the "solution" code from my question). So I should now change that function so that it simulate a tty every time and for every call, just because `info` does not work as expected? – winklerrr Jul 02 '21 at 11:02
  • For me, it looks like a bug. It is that `info -o /root/somefile get` exits with success, while the command failed. `I should now change...` I would handle only specifically `info` specially, workarounds, like `if [[ "$1" == "info" ]]; then handle info specially`, bugs happen. Then when bug is fixed, you would `if [[ "$1" == "info" ]] && info_version lower_then something; then handle info specially`. – KamilCuk Jul 02 '21 at 11:04
0

Instead of checking the exit status of the command, I ended up with saving the output and simply checking if there is any output that could be used for further processing (in my case piping into less):

my_less () {
  output=$("$@")
  if [[ ! -z "$output" ]]; then
    printf '%s' "$output" | less
  fi
}

Even with the bug in info, my function now works as the bug only affects the command's exit status. It's error messages are written to stderr as expected, so I'm using that behavior.

winklerrr
  • 13,026
  • 8
  • 71
  • 88