To store the output of a command in a variable, you can use the shell command substitution feature in the forms below: variable_name=$(command) variable_name=$(command [option ...] arg1 arg2 ...) OR variable_name='command' variable_name='command [option ...]
It would be neater to capture the error file thus:
ERROR=$(</tmp/Error)
The shell recognizes this and doesn't have to run 'cat
' to get the data.
The bigger question is hard. I don't think there's an easy way to do it. You'd have to build the entire pipeline into the sub-shell, eventually sending its final standard output to a file, so that you can redirect the errors to standard output.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
Note that the semi-colon is needed (in classic shells - Bourne, Korn - for sure; probably in Bash too). The '{}
' does I/O redirection over the enclosed commands. As written, it would capture errors from sed
too.
WARNING: Formally untested code - use at own risk.
Redirected stderr to stdout, stdout to /dev/null, and then use the backticks or $()
to capture the redirected stderr:
ERROR=$(./useless.sh 2>&1 >/dev/null)
This will allow you to pipe the output of your useless.sh
script through a command such as sed
and save the stderr
in a variable named error
. The result of the pipe is sent to stdout
for display or to be piped into another command.
It sets up a couple of extra file descriptors to manage the redirections needed in order to do this.
#!/bin/bash
exec 3>&1 4>&2 #set up extra file descriptors
error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )
echo "The message is \"${error}.\""
exec 3>&- 4>&- # release the extra file descriptors
There are a lot of duplicates for this question, many of which have a slightly simpler usage scenario where you don't want to capture stderr and stdout and the exit code all at the same time.
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
works for the common scenario where you expect either proper output in the case of success, or a diagnostic message on stderr in the case of failure.
Note that the shell's control statements already examine $?
under the hood; so anything which looks like
cmd
if [ $? -eq 0 ], then ...
is just a clumsy, unidiomatic way of saying
if cmd; then ...
For the benefit of the reader, this recipe here
If you want to catch stderr
of some command
into var
you can do
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Afterwards you have it all:
echo "command gives $? and stderr '$var'";
If command
is simple (not something like a | b
) you can leave the inner {}
away:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Wrapped into an easy reusable bash
-function (probably needs version 3 and above for local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Explained:
local -n
aliases "$1" (which is the variable for catch-stderr
)3>&1
uses file descriptor 3 to save there stdout points{ command; }
(or "$@") then executes the command within the output capturing $(..)
2>&1
redirects stderr
to the output capturing $(..)
1>&3
redirects stdout
away from the output capturing $(..)
back to the "outer" stdout
which was saved in file descriptor 3. Note that stderr
still refers to where FD 1 pointed before: To the output capturing $(..)
3>&-
then closes the file descriptor 3 as it is no more needed, such that command
does not suddenly has some unknown open file descriptor showing up. Note that the outer shell still has FD 3 open, but command
will not see it.lvm
complain about unexpected file descriptors. And lvm
complains to stderr
- just what we are going to capture!You can catch any other file descriptor with this recipe, if you adapt accordingly. Except file descriptor 1 of course (here the redirection logic would be wrong, but for file descriptor 1 you can just use var=$(command)
as usual).
Note that this sacrifices file descriptor 3. If you happen to need that file descriptor, feel free to change the number. But be aware, that some shells (from the 1980s) might understand 99>&1
as argument 9
followed by 9>&1
(this is no problem for bash
).
Also note that it is not particluar easy to make this FD 3 configurable through a variable. This makes things very unreadable:
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Security note: The first 3 arguments to
catch-var-from-fd-by-fd
must not be taken from a 3rd party. Always give them explicitly in a "static" fashion.So no-no-no
catch-var-from-fd-by-fd $var $fda $fdb $command
, never do this!If you happen to pass in a variable variable name, at least do it as follows:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
This still will not protect you against every exploit, but at least helps to detect and avoid common scripting errors.
Notes:
catch-var-from-fd-by-fd var 2 3 cmd..
is the same as catch-stderr var cmd..
shift || return
is just some way to prevent ugly errors in case you forget to give the correct number of arguments. Perhaps terminating the shell would be another way (but this makes it hard to test from commandline).exec
, but then it gets really ugly.bash
as well such that there is no need for local -n
. However then you cannot use local variables and it gets extremely ugly!eval
s are used in a safe fashion. Usually eval
is considerered dangerous. However in this case it is no more evil than using "$@"
(to execute arbitrary commands). However please be sure to use the exact and correct quoting as shown here (else it becomes very very dangerous).If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With