1565

Is there a standard Bash tool that acts like echo but outputs to stderr rather than stdout?

I know I can do echo foo 1>&2 but it's kinda ugly and, I suspect, error prone (e.g. more likely to get edited wrong when things change).

Zombo
  • 1
  • 62
  • 391
  • 407
BCS
  • 75,627
  • 68
  • 187
  • 294

15 Answers15

2028

You could do this, which facilitates reading:

>&2 echo "error"

>&2 copies file descriptor #2 to file descriptor #1. Therefore, after this redirection is performed, both file descriptors will refer to the same file: the one file descriptor #2 was originally referring to. For more information see the Bash Hackers Illustrated Redirection Tutorial.

Marco Aurelio
  • 20,724
  • 1
  • 17
  • 16
  • 5
    I learn this trick quite a while ago. This page has some good information to it. http://www.tldp.org/LDP/abs/html/io-redirection.html – Marco Aurelio Oct 03 '14 at 06:45
  • 65
    @BCS I dunno about using an `alias` in a shell script. It would probably be safer to use `errcho(){ >&2 echo $@; }` – Braden Best Jul 13 '15 at 21:52
  • 12
    >&2 is normally put at the end. This will work, but its used less often – Iskren Ivov Chernev Sep 08 '15 at 23:59
  • 302
    In the nearly 40 years that I've been using Unix-like systems it has never occurred to me that you could put the redirect anywhere but at the end. Putting it up front like this makes it much more obvious (or "facilitates reading" as @MarcoAurelio says). +1 for teaching me something new. – Hephaestus Nov 05 '15 at 15:07
  • 1
    FYI: if you want to format or do anything besides simply echo the string then you'll have to move the redirect back to the end. For example `errcho(){ >&2 echo $@|pr -To5;}` won't work. To do something like that you'll have to put the redirect somewhere after the last pipe like: `errcho(){ echo $@|>&2 pr -To5;}` – Jon Red Mar 13 '20 at 18:05
  • 1
    This also works with printf; `>&2 printf "size=%8d\n" $size` This less likely to be part of a pipeline. – RichTBreak Jul 23 '20 at 22:07
  • 32
    I always had troubles to understand how to read bash redirections but I think I've finally found the correct way to read them: It's important to note that 1 and 2 file descriptors are already pointing somewhere so when you write `2>&1` it means `point 2 where 1 is _currently_ pointing to` so now if you wrongly write `2>&1 >file`, it reads `point 2 where 1 is already going and point 1 to a file` so 2 now goes where 1 used to go but 1 no longer goes there... On the contrary `>file 2>&1` reads `point 1 to this file and point 2 to where 1 is currently pointing to`. `&` can thus be read as `where`. – 2072 Jan 24 '21 at 17:41
  • Would be nice to have native perror for bash – lamino Apr 01 '21 at 00:34
  • Is this valid shellscript? – theonlygusti Jun 17 '23 at 16:14
527

You could define a function:

echoerr() { echo "$@" 1>&2; }
echoerr hello world

This would be faster than a script and have no dependencies.

Camilo Martin's bash specific suggestion uses a "here string" and will print anything you pass to it, including arguments (-n) that echo would normally swallow:

echoerr() { cat <<< "$@" 1>&2; }

Glenn Jackman's solution also avoids the argument swallowing problem:

echoerr() { printf "%s\n" "$*" >&2; }
James
  • 6,978
  • 2
  • 17
  • 18
  • 9
    I must say that echo is kinda unreliable. `echoerr -ne xt` is not going to print "-ne xt". Better use `printf` for that. – Camilo Martin Jun 24 '14 at 13:23
  • 13
    Oh, you can actually use cat too: `echoerr() { cat <<< "$@" 1>&2; }` – Camilo Martin Jun 24 '14 at 13:26
  • 1
    and do an `export -f echoerr` if you want any subshells to pick up the function – Avindra Goolcharan Mar 11 '15 at 19:39
  • 9
    Or, `printf "%s\n" "$*" >&2` – glenn jackman Mar 13 '16 at 16:34
  • @CamiloMartin `cat <<< "$@"` is even worse; it condenses all spaces between arguments into one, so you have to quote what you pass to it for reliability. – GKFX Sep 22 '16 at 19:01
  • 2
    @GKFX no it doesn't, `echoerr 'foo ​ bar'` will have two spaces between `foo` and `bar`. *(I had to add a zero-width joiner to make it show two spaces in this comment, so don't copy and paste it or it will be three spaces in the console).* – Camilo Martin Sep 29 '16 at 12:49
  • @CamiloMartin Putting the double space in quotes (or escaping it etc.) works fine, but the first code block in this answer didn't have quotes. I meant `echoerr a ​ b`. However, I've just checked and the real `echo` command behaves the same as yours so clearly this is not a bug. – GKFX Sep 29 '16 at 13:20
  • 9
    @GKFX Of course it only works correctly when quoting. Why people don't quote their strings is beyond me. (when you don't quote, everything separated by one or more `$IFS` whitespace is sent as a separate argument, which in the case of `echo` means concatenating them with `0x20`s, but the dangers of not quoting far outweight the convenience of 2 less characters to type). – Camilo Martin Sep 29 '16 at 19:56
  • 1
    @CamiloMartin I see that now; I got used to the unquoted form from seeing it so much and never realised the pitfalls. – GKFX Sep 30 '16 at 17:52
  • 2
    @GKFX It's a common mistake (unfortunately). On that regard, remember to use single-quotes when you don't want anything to be interpreted (not just `$variables`, but consider as well `echo "why!?"` vs `echo 'why!?'`, also note how `echo '\'` is correct). Bash is beautiful once you remember those things by heart, but it is held back by misunderstandings of its peculiarities and the bugs they cause (the same thing that happens with Javascript, which [came a long way](http://es6-features.org) but is still shunned by people who got stuck with the bad first impression). – Camilo Martin Oct 01 '16 at 20:59
  • Makes sense to erase the unsafe answer ($@), and only leave out the two last correct ones. – VasiliNovikov May 17 '18 at 07:47
  • you can still use echo (and not consume leading switches) with something like `echoerr() { echo $'\033' $@ 1>&2; }` or maybe `echoerr() { echo $' \010'$@ 1>&2; }`. these will appear as expected when printed to the terminal, but those escaped chars *do* get output, even if you don't see them – ardnew Apr 03 '20 at 02:15
436

Since 1 is the standard output, you do not have to explicitly name it in front of an output redirection like >. Instead, you can simply type:

echo This message goes to stderr >&2

Since you seem to be worried that 1>&2 will be difficult for you to reliably type, the elimination of the redundant 1 might be a slight encouragement to you!

Pang
  • 9,564
  • 146
  • 81
  • 122
Brandon Rhodes
  • 83,755
  • 16
  • 106
  • 147
83

Another option

echo foo >>/dev/stderr
Robin Winslow
  • 10,908
  • 8
  • 62
  • 91
Zombo
  • 1
  • 62
  • 391
  • 407
38

No, that's the standard way to do it. It shouldn't cause errors.

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • 10
    *It* shouldn't cause errors, but I might be more likely to. OTOH it's not that big a deal. – BCS Jun 07 '10 at 14:42
  • 1
    The only way it will fail is if the file ID for stdout is not 1 or the file ID for stderr is not 2. As these constants are defined by POSIX.1, which I believe is a prerequisite for bash to build, this will work for the forseeable future. Thus, I am curious as to what you mean by "likely to get edited wrong when things change". Are you having a similar problem with people changing the word "echo" into something else? – Mike DeSimone Jun 07 '10 at 14:52
  • 6
    @Mike DeSimone: If someone else messes with the code, shuffles around the output, and doesn't actually know bash, they could easily drop (or mistype) the `1>&2`. We all wish this wouldn't happen, but I'm sure we've all been places where it does. – Cascabel Jun 07 '10 at 15:00
  • 3
    `( echo something 1>&2 ; something else ) > log` -> `(echo something; cp some junk 1>&2 ; something else) > log` Oops. – BCS Jun 07 '10 at 17:15
  • 35
    IMHO, if someone messes with the code and doesn't know bash, this may be the least of your problems. – Mike DeSimone Jun 07 '10 at 17:34
  • 9
    I think if that's likely to be an issue, you should start using a different language: trying to make bash foolproof is a fool's venture. – intuited Jun 07 '10 at 23:19
32

If you don't mind logging the message also to syslog, the not_so_ugly way is:

logger -s $msg

The -s option means: "Output the message to standard error as well as to the system log."

Grzegorz Luczywo
  • 9,962
  • 1
  • 33
  • 22
  • 4
    this is great! how portable is it? – code_monk Jul 28 '16 at 12:39
  • 3
    @code_monk: The logger command is expected to be IEEE Std 1003.2 ("POSIX.2") compatible, The logger command is part of the util-linux package and is available from Linux Kernel Archive ⟨https://www.kernel.org/pub/linux/utils/util-linux/⟩. – miku May 20 '19 at 16:52
25

Another option that I recently stumbled on is this:

    {
        echo "First error line"
        echo "Second error line"
        echo "Third error line"
    } >&2

This uses only Bash built-ins while making multi-line error output less error prone (since you don't have to remember to add &>2 to every line).

GuyPaddock
  • 2,233
  • 2
  • 23
  • 27
  • 1
    Can't believe it, you vote me down when I recommend to use bash-redirect and in your own answer you are using bash-redirect. – return42 Feb 20 '19 at 08:33
  • 2
    @return42 I voted your answer down because all it did was tell the OP that there's no better answer than what they started with.. it's not really an answer. I also don't see a sub-shell suggestion in your answer... your answer really just advises the OP not to use `cat` or any other utility, which is off-topic for the question. – GuyPaddock Nov 27 '19 at 19:09
  • what did I said: https://stackoverflow.com/questions/2990414/echo-that-outputs-to-stderr/54738946?noredirect=1#comment96341291_26279455 – return42 Nov 28 '19 at 20:13
21

Note: I'm answering the post- not the misleading/vague "echo that outputs to stderr" question (already answered by OP).

Use a function to show the intention and source the implementation you want. E.g.

#!/bin/bash

[ -x error_handling ] && . error_handling

filename="foobar.txt"
config_error $filename "invalid value!"

output_xml_error "No such account"

debug_output "Skipping cache"

log_error "Timeout downloading archive"

notify_admin "Out of disk space!"

fatal "failed to open logger!"

And error_handling being:

ADMIN_EMAIL=root@localhost

config_error() { filename="$1"; shift; echo "Config error in $filename: $*" 2>&1; }

output_xml_error() { echo "<error>$*</error>" 2>&1; }

debug_output() { [ "$DEBUG"=="1" ] && echo "DEBUG: $*"; }

log_error() { logger -s "$*"; }

fatal() { which logger >/dev/null && logger -s "FATAL: $*" || echo "FATAL: $*"; exit 100; }

notify_admin() { echo "$*" | mail -s "Error from script" "$ADMIN_EMAIL"; }

Reasons that handle concerns in OP:

  • nicest syntax possible (meaningful words instead of ugly symbols)
  • harder to make an error (especially if you reuse the script)
  • it's not a standard Bash tool, but it can be a standard shell library for you or your company/organization

Other reasons:

  • clarity - shows intention to other maintainers
  • speed - functions are faster than shell scripts
  • reusability - a function can call another function
  • configurability - no need to edit original script
  • debugging - easier to find the line responsible for an error (especially if you're deadling with a ton of redirecting/filtering output)
  • robustness - if a function is missing and you can't edit the script, you can fall back to using external tool with the same name (e.g. log_error can be aliased to logger on Linux)
  • switching implementations - you can switch to external tools by removing the "x" attribute of the library
  • output agnostic - you no longer have to care if it goes to STDERR or elsewhere
  • personalizing - you can configure behavior with environment variables
Cezary Baginski
  • 2,077
  • 15
  • 14
18

My suggestion:

echo "my errz" >> /proc/self/fd/2

or

echo "my errz" >> /dev/stderr

echo "my errz" > /proc/self/fd/2 will effectively output to stderr because /proc/self is a link to the current process, and /proc/self/fd holds the process opened file descriptors, and then, 0, 1, and 2 stand for stdin, stdout and stderr respectively.

The /proc/self link doesn't work on MacOS, however, /proc/self/fd/* is available on Termux on Android, but not /dev/stderr. How to detect the OS from a Bash script? can help if you need to make your script more portable by determining which variant to use.

Sled
  • 18,541
  • 27
  • 119
  • 168
Sebastian
  • 4,770
  • 4
  • 42
  • 43
  • 6
    The `/proc/self` link doesn't work on MacOS, so I'll stick with the more straight-forward `/dev/stderr` method. Also, as noted in other answers/comments, it is probably better to use `>>` to append. – MarkHu Nov 22 '17 at 00:30
  • 5
    `/proc/self/fd/*` is available on Termux on Android, but not `/dev/stderr`. – go2null Feb 01 '18 at 11:30
  • I used that to test on my current system it could be modified to test in different contexts to accomplish other tasks than echoing. Using this: `echo -n "has '/proc/self/fd/2': " ; [ -e /proc/self/fd/2 ] && echo true || echo false` or using that: `echo -n "has '/dev/stderr': " ; [ -e /dev/stderr ] && echo true || echo false` – Benjamin Vincent Dec 05 '20 at 12:02
16

Don't use cat as some have mentioned here. cat is a program while echo and printf are bash (shell) builtins. Launching a program or another script (also mentioned above) means to create a new process with all its costs. Using builtins, writing functions is quite cheap, because there is no need to create (execute) a process (-environment).

The opener asks "is there any standard tool to output (pipe) to stderr", the short answer is : NO ... why? ... redirecting pipes is an elementary concept in systems like unix (Linux...) and bash (sh) builds up on these concepts.

I agree with the opener that redirecting with notations like this: &2>1 is not very pleasant for modern programmers, but that's bash. Bash was not intended to write huge and robust programs, it is intended to help the admins to get there work with less keypresses ;-)

And at least, you can place the redirection anywhere in the line:

$ echo This message >&2 goes to stderr 
This message goes to stderr
Marcel Gosselin
  • 4,610
  • 2
  • 31
  • 54
return42
  • 543
  • 3
  • 10
  • 12
    Telling devs not to use programs only because of performance reason is premature optimization. Elegant, easy-to-follow approaches should preferred over hard-to-understand code that performs better (on the order of milliseconds). – GuyPaddock Feb 18 '19 at 00:01
  • 1
    @GuyPaddock sorry, you haven't read through this properly. Firs; Its about redirecting pipes which is well handled by the bash. If one do not like the (ugly) syntax how bash redirects, he should stop implementing bash scripts or learn the bash way. Second; you should know how expensive it is to launch a new prozess compared to yust call a bash builtin. – return42 Feb 18 '19 at 12:26
  • 5
    There's a difference between letting someone know the performance trade-offs of Bash built-ins vs `cat` and instructing someone not to use cat because it's slow. There are countless use cases where cat is the right choice, so that's why I object to your answer. – GuyPaddock Feb 18 '19 at 21:56
  • @GuyPaddock The opener asked for a `echo` replacement. Even if he use `cat`, he has to use a bash redirect. anyway. So, there is absolute no sense in to use `cat` here. BTW I use `cat` 100 times a day, but never in the context the opener asked for... you got it? – return42 Feb 20 '19 at 07:55
10

This is a simple STDERR function, which redirect the pipe input to STDERR.

#!/bin/bash
# *************************************************************
# This function redirect the pipe input to STDERR.
#
# @param stream
# @return string
#
function STDERR () {

cat - 1>&2

}

# remove the directory /bubu
if rm /bubu 2>/dev/null; then
    echo "Bubu is gone."
else
    echo "Has anyone seen Bubu?" | STDERR
fi


# run the bubu.sh and redirect you output
tux@earth:~$ ./bubu.sh >/tmp/bubu.log 2>/tmp/bubu.err
erselbst
  • 161
  • 1
  • 6
8

Combining solution suggested by James Roth and Glenn Jackman

  • add ANSI color code to display the error message in red:
echoerr() { printf "\e[31;1m%s\e[0m\n" "$*" >&2; }

# if somehow \e is not working on your terminal, use \u001b instead
# echoerr() { printf "\u001b[31;1m%s\u001b[0m\n" "$*" >&2; }

echoerr "This error message should be RED"
Polymerase
  • 6,311
  • 11
  • 47
  • 65
7

read is a shell builtin command that prints to stderr, and can be used like echo without performing redirection tricks:

read -t 0.1 -p "This will be sent to stderr"

The -t 0.1 is a timeout that disables read's main functionality, storing one line of stdin into a variable.

Douglas Mayle
  • 21,063
  • 9
  • 42
  • 57
5

Make a script

#!/bin/sh
echo $* 1>&2

that would be your tool.

Or make a function if you don't want to have a script in separate file.

BCS
  • 75,627
  • 68
  • 187
  • 294
n0rd
  • 11,850
  • 5
  • 35
  • 56
  • 6
    Better for it to be a function (like James Roth's answer), and better to pass along all arguments, not just the first. – Cascabel Jun 07 '10 at 14:59
  • 2
    Why would a function be better? (Or, alternatively: "Better to explain why it would be better...") – Ogre Psalm33 May 20 '14 at 14:44
  • 5
    @OgrePsalm33 One reason a function would be better is that when calling a script, usually a new shell instance is created to provide an environment in which to execute the script. A function, on the other hand, is placed into the currently running shell's environment. Calling a function, in this case, would be a much more efficient operation since the creation of another instance of a shell would be avoided. – Dennis Estenson Dec 01 '15 at 03:52
3

Here is a function for checking the exit status of the last command, showing error and terminate the script.

or_exit() {
    local exit_status=$?
    local message=$*

    if [ "$exit_status" -gt 0 ]
    then
        echo "$(date '+%F %T') [$(basename "$0" .sh)] [ERROR] $message" >&2
        exit "$exit_status"
    fi
}

Usage:

gzip "$data_dir"
    or_exit "Cannot gzip $data_dir"

rm -rf "$junk"
    or_exit Cannot remove $junk folder

The function prints out the script name and the date in order to be useful when the script is called from crontab and logs the errors.

59 23 * * * /my/backup.sh 2>> /my/error.log
Miroslav Popov
  • 3,294
  • 4
  • 32
  • 55