Reputation: 1478
in a bash script I need to capture the output of a command executed with eval
and also the exit code of the same command.
output=$(eval "$test_cmd" 2>&1)
result=$?
$test_cmd
is actually the command line for running a python script. The command string itself contains strings surrounded by quotation marks. I tried running it without eval
and it did not work. It may be that the problem was trivial but I didn't investigate, knowing that it worked with eval
.
The command above works but I often get a warning:
warning: command substitution: ignored null byte in input
If I have interpreted correctly it depends on the output of the command executed.
So I tried this:
output=$(eval "$test_cmd" 2>&1 | tr -d '\0')
result=$?
The warning disappears, but this way $?
is that of tr
and not of my command.
I considered using ${PIPESTATUS[0]}
instead of $?
, but it wouldn't work because the pipe is inside another shell.
I ended up doing this:
output=$( eval "$test_cmd" 2>&1 | tr -d '\0'; return ${PIPESTATUS[0]} )
result=$?
From early tests it seems to work, but I ask are there drawbacks, is this the right way or are there better ways.
Upvotes: 1
Views: 85
Reputation: 203502
It sounds like you got to this point by storing a command in a scalar variable and executing it with eval
. Rather than investigating a workaround for this aspect of the problems introduced by that workaround (bandaids on top of bandaids) you should instead just fix the problem that got you here in the first place, i.e. storing a command in a scalar variable, so you don't need eval
and then you don't have your current problem (plus others you may not have tripped over yet).
I assume you're doing something like this:
test_cmd='date +"%F %T"'
output=$(eval "$test_cmd" 2>&1)
result=$?
when you just need to store the command in an array instead, see http://mywiki.wooledge.org/BashFAQ/050, something like:
test_cmd=( date +"%F %T" )
output=$( "${test_cmd[@]}" 2>&1 ); result=$?
I also moved result=$?
to the end of the line where test_cmd
executes as that's best practice (after just not saving the exit status and instead testing the result of executing the command inline) so it's harder for anyone to accidentally add a echo "$output"
or similar between the two which would change the value of $?
before you save it.
As for the warning: command substitution: ignored null byte in input
warning - if test_cmd
is producing undesirable NULs, don't try to solve that problem everywhere you call test_cmd
, wrap it in a function (or script) and then call THAT instead, e.g.:
run_test_cmd() {
"${test_cmd[@]}" 2>&1 | tr -d '\0'
return "${PIPESTATUS[0]}"
}
output=$( run_test_cmd ); result=$?
Upvotes: 1
Reputation: 19555
Avoiding the use of eval has already been discussed by multiple contributors, so let's skip that part and focus on addressing your problem.
For documentation and testing purposes, let’s create a simple function that outputs some text containing a null character and sets a specific return code:
test_cmd() {
printf 'Hello World\0\n'
return 42
}
If you run the following commands:
output=$(test_cmd)
result=$?
You will indeed see a warning about the null character in the output.
To suppress this warning, you can execute the function within a command group and redirect the stderr
of the entire group, like so:
#!/usr/bin/env bash
{ output=$(test_cmd);} 2>/dev/null # Suppresses the null character warning
result=$?
# Check the captured output and return value
declare -p output result
The output confirms that these commands correctly capture the output (ignoring the null character and removing the trailing newline) without any kind of warning, and also capture the return value:
declare -- output="Hello World"
declare -- result="42"
Upvotes: 0