Honestly, I'm fairly confident that there is no problem which isn't better solved by doing something other than explicitly invoking an external binary (especially looking for a specific implementation of an external binary).
So as much I normally hate answers that boil down to "you should never need to do this thing you want to do", I'm making an exception here. I will instead suggest numerous alternatives, in order of how strongly I suggest them. If you absolutely must find the right echo
binary, Michael Homer has the most appropriate answer, and you should read Stéphane Chazelas' answer too because it brings up several locations in a file-system that you might not expect to find echo
binaries. I also have some additional caveats about searching for the "right" echo in my last section of this answer.
printf
I have never seen a system which was intended to actually run custom shell scripts and which saw real use in the last couple of decades which didn't ship with a printf
. I have certainly never seen a system which came even close to including something as large as GNU coreutils
which didn't ship with a printf
out of the box.
For perspective, I am obsessed to the point it's unhealthy with shell script portability, and I have access to literally only two systems with a Bourne-like shell right now which don't have printf
: A virtualized Unix v7 (yes, the one from four decades ago or so), and one (out of about five in my possession) Android device which basically has nothing installed and is so locked down it's not going to be running any useful shell scripts any time soon anyway.
This will print your string exactly, on - I promise - every system that deserves to be used by anyone in modern times:
printf '%s' "$my_var_holding_my_text"
and
printf '%s' 'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings'
UNLESS you also need to print out null bytes. I doubt you need to. If you do, you can't have the entire text as one argument to printf anyway, since most shells (zsh
deserves praise here) use null bytes as string terminators. So you'd use \000
octal escapes within your format string (the first argument) and combine that with zero or more %s
and zero or more further arguments to print all the other text. Hex escapes (vs octal) and other tricks are less portable, as far as I know.
Suggestion: Don't put anything you don't need specially parsed/converted into the format string. Different printf
implementations support slightly different formatting (including modern printf
implementations, e.g. bash
builtin vs busybox printf
).
If you want the extra newline appended to your output, an extra \n
is trivial to add to your format string:
printf '%s\n' foo
is a strictly unambiguous/works-the-same-everywhere equivalent of
echo foo
If you get some convoluted situation where it's not easy to both construct the format string you need (remember you can build the format string programmatically using variables too), you can always include the newline literal into the arguments you're passing to printf
, or output newline characters by themselves with a bare echo
with no arguments separately.
Here-files, or: cat <<DELIMITER
cat <<DELIMITER
$my_variable_containing_my_text
DELIMITER
or
cat <<DELIMITER
my text so long as it doesn't include a line starting with DELIMITER
because that's going to be used as the end-of-file for the here-file.
$my_variable_containing_the_word_DELIMITER
but sticking it in a variable should work fine in all shells I know of
DELIMITER
The one caveat is that you can't control if you get a newline at the end or not: You always will get a newline at the end. Most of the time, this is probably what you wanted or it doesn't matter. Also, many (all?) shells use temporary files on disk to implement here-files, so it's possible to run into a situation where a very restricted system doesn't allow this (the same obscenely crippled Android instance without printf
I have also has SELinux policies, or some other permissions limitation (I don't remember precisely) which prevent the shell from creating temporary files).
Because of this, on a computer-security note, if you need to print sensitive information, a here-file could either be worse or better than an echo
, depending on the exact system (is echo
external or a builtin? is /proc/$PID world or user readable? are here-files user or world readable?), and your exact threat-model (is your threat more likely to forensically search your disk than your running process information?)
expr
A little-known feature of expr
is that it can extract and print substrings from within an argument with a regex match. This is basically a more portable version of the original echo
behavior (print contents verbatim, and one newline character), and is an even more portable way to print plain text than printf
:
expr X"$my_var_holding_my_text" : 'X\(.*\)'
and
expr X'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings' : 'X\(.*\)'
This works back to Unix v7. The X
at the front of both the string/variable to print and at the front of the regex outside of the \( \)
sub-pattern match/selection is important: the former keeps the value you're printing from getting mis-interpreted by the expr
command as an expr
keyword, and the latter then makes sure the X isn't actually printed.
awk
Here's a compact awk
one-liner which will print most single-string arguments it receives unambiguously (you'll still have a problem with backslashes on more recent version of awk
- thanks for Stephan for reminding me of this in the comments):
: | awk 'BEGIN { ORS="" } END { print v }' v="$my_var_with_my_string"
This works back to Unix v7. If you don't have backslashes, this is extremely portable, and might be good enough for the text you need to output. You might also find writing feature-tests for different awk
implementations in your scripts easier/simpler/cleaner than making echo
work for you, since while there are definitely many deviations amongst awk
s, there's less variations to test for than echo
if your core goal is just writing some exact output.
Obviously use the single-quoted string technique if you want to use a literal instead of a variable. Do an echo
without arguments if you want a newline after it (or take the time to rigorously vet a specific way to ensure a newline gets printed by the awk
command - I suggest replacing the :
no-op command on the left of the pipe with echo
without arguments, but I haven't carefully audited that idea for across-the-board portability)..
echo
piped through sed
or similar
If you know that your inputs are not special (no backslashed octal escapes like \000
in the input you want to print literally, and you need to just avoid specially parsing a -
character, for example, you want to print -e
, you can still make arbitrary echo
work for you if you have something else you can use to preprocess echo
's output:
echo X-e | sed '1 s/^X//'
For limited, well-defined inputs, you might be able to get away with trivial sed
replacements like this. Depending on exactly what you need, it might get progressively harder. At a certain point, it's better to move onto the next alternative:
Feature-test echo
The idea that you cannot reliably make echo
print exactly what you want, if you're willing to go through pains to do it, is not necessarily true, especially if you have a well-known set of outputs you need. And trust me, it's going to be less painful than searching for just the right echo
binary somewhere in the file system.
You particularly expressed a concern about reliably printing a -
character. Unfortunately, I haven't yet written a thorough echo
-feature-testing shell script snippet, but here's some basic snippets off-the-top-of-my-head:
minus=
case `echo -` in '-')
minus=-
esac
# if echo handles a literal minus correctly, $minus is now non-blank
case `echo '\055'` in
'-')
minus='\055'
esac
# if echo parses backslashed escapes by default, $minus
# is now the correct octal backslash escape for ASCII "-"
You can construct similar tests for specific things: echo -e '\055'
(shoul either output -e \055
or -
), echo -E '\055'
(if it is parsing backslash escapes by default and you want to try turning them off), etc.
Many modern instances of echo will parse other backslash escapes than octal numbers, but you can either feature-test specifically for those (echo '\x2d'
or whatever) - but I think in most cases, you really just want to find the set of arguments that you can pass to echo to make it print things without doing special substitutions on the contents, then feed it the output you want verbatim.
Depending on your needs, echo -n
might also be worth testing for, but bear in mind that command substitution always strips off the last newline (just the last one on most shells, but all trailing newlines on some shells), so your two likely output options are the literal -n
and the empty string.
You may also want to consult autoconf
and m4
sources, because I think those tools go out of their way to find an echo they can use to do unambiguous printing if they can't find a printf
or something else that works.
Literally anything else
I genuinely think just about anything which doesn't depend on you having to brute-force-search for exactly the right echo
will be best. It's extremely likely that particular echo
won't be installed, or won't be installed where you look, or that an automatic bruteforce search starting from /
will bring some poor chap's system down to a crawl.
And while very unlikely, it's possible a binary will pass your finger-printing for being GNU coreutils
echo
, but will have a behavioral difference: even if GNU never changes their implementation, someone might wrap their own installed version of GNU's echo
to not do what they consider to be a dumb behavior (transparently passing through all arguments except for silently dropping the ones that are special while setting the ones they want is trivial in a shell script, so you could easily have echo --help
print the right text but echo -e '\055'
does the wrong thing). And no, not even a binary that passes thorough fingerprinting is certain: I have edited raw ELF binaries to change behavior before, and I will do it again. Sometimes it's to enable very useful functionality (not silently dropping messages containing non-ASCII bytes, like Unicode smileys, in closed-source messaging software) and sometimes for really petty things, like changing the hardcoded default PS1
in shells to be \$\
instead of \w \$
). I personally don't have enough of a reason to do it to echo
because on systems I actually use, I just ignore echo
for almost all serious work, but someone else might feel as strongly about default echo
behavior as I feel about default PS1
variable values. So you're back to feature-testing echo
, at which point see the above section.
Also, please note that I have systems where GNU coreutils echo
is installed as gecho
, so neither an efficient search of the PATH
and likely install locations, nor a bruteforce search for only files named echo
, will catch those systems.
And I'd actually bet that more systems have some scripting language like perl
installed, which can do what you want, than systems which have GNU coreutils
echo
specifically: some scripting languages are ubiquitous and mostly have one implementation or a well-defined spec, while echo
implementations are myriad and follow precisely one spec: "do something slightly different than as many other echo
implementations as possible".
$(find / -perm +001 -type f -exec sh -c 'strings "$1" | grep -q "GNU coreutils" && strings "$1" | grep -q "Echo the STRING(s) to standard output." && echo "$1"' sh {} \; | head -n 1)
? – Michael Homer Apr 08 '17 at 00:05printf
). – Michael Homer Apr 08 '17 at 00:09printf()
is simply a wrapper around the Cprintf
function, which is part of the C standard library; you pretty much can't get any more portable than that. – Barton Chittenden Apr 08 '17 at 05:40env echo ...
will find and invoke it (unless there is a serious problem, e.g. with PATH) – VPfB Apr 08 '17 at 07:00printf
(you can, unless you're running on systems which won't have a singlecoreutils
binary on them, like Unix v7 or the stripped down and ridiculously locked down Android builds). You are still better off figuring out how to portably test-and-use whatever-echo-you-got: it's much more likely to succeed than finding a specificecho
implementation. – mtraceur Apr 08 '17 at 11:06\echo work
(that is a back slash), for all shells? – ctrl-alt-delor Apr 08 '17 at 12:44busybox
sh
/ash
will invoke its own builtinecho
instead of an externalecho
binary when given\echo
. – mtraceur Apr 08 '17 at 13:00\echo
worked syntactically to invoke something/anything namedecho
, in which case, it should be syntactically valid in all Bourne-likes. Just no guarantees about what exactly it'll invoke (alias, function, builtin, or external binary) that I would rely on. – mtraceur Apr 08 '17 at 13:04env
isn't in$PATH
? – cat Apr 08 '17 at 22:51env
not in$PATH
is an error condition that would be noticed very quickly. – VPfB Apr 09 '17 at 05:23