why does "echo -n" command not work in a .sh script? - bash

For example, I'm writing a script in a .sh file, and I want to echo the phrase "string" without a trailing whitespace/new line, so I should probably use echo -n "string" like how I would achieve this function in bash or zsh. However, when I run the .sh in bash/zsh with sh command the .sh file would just print -n string, with a new line included. How do I print something without trailing new line whilst also using the echo command?

bash's implementation of the echo command differs wildly from the POSIX specification. When bash is run as sh, echo behaves more like POSIX intends, but your sh may also not be bash, but a shell like dash that adheres more strictly to the POSIX specification, which does not allow for any extensions. POSIX requires -n to be treated like any other argument, something to be output rather than modifying the behavior of echo.
Use printf instead, which has a tighter POSIX specification and is much more portable.
printf 'string'

Related

zsh variable substitution modifies surrounding string

Why does the "c" character go missing in the following example?
var="\bINSERT"
echo abc${var}def
> abINSERTdef
Is there any documentation that tells me how to do similar things or disable the behaviour?
I can't find any shell variables documentation.
The command substitution isn't doing anything wrong -- it's echo whose behavior is surprising (legally; POSIX allows, but does not require, echo to interpret backslash escape sequences by default, so zsh is not in the wrong here).
printf '%s\n' abc${var}def
...doesn't have your problem (but for portability to non-zsh shells, I would quote "abc${var}def").
See Why is printf better than echo? on Unix & Linux Stack Exchange, and the POSIX spec for echo at https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html
As already was mentioned in the comments, the zsh implementation of the internal echo differs from the one implemented by the external echo command.
If you want to continue using echo, there are several workarounds:
Do a echo -E ...; the -E turns off the backslash interpretation.
Do a command echo ...; the keyword command forces the shell to use the external echo command.
Set in the shell the option set bsd_echo and then just leave the echo command as it is. The option tells the shell to not use by default the backslash interpretation for its internal echo command.

Why are the results of echo -n "..." different when my script is started different ways?

I have
$>echo -n "\033[40m "
If I evoke it on command line, vs. save it to a file and evoke the file on command line, the result would be different.
I am using zsh on mac, but expect this to also work with bash. Shouldn't output be exactly the same? If not, why?
The behavior of echo -n is different between shells (and zsh and bash are indeed different shells) because it is, literally, undefined behavior: The POSIX sh specification does not require a shell to behave in any particular way when processing it:
The following operands shall be supported:
string - A string to be written to standard output. If the first operand is -n, or if any of the operands contain a backslash ( \ ) character, the results are implementation-defined.
When using XSI extensions, echo is required to process backslash-escape sequences like \033 with no further arguments given; without those extensions, it isn't required to process those sequences at all, but is purely implementation-defined behavior (thus dependent on your specific shell).
As a well-defined alternative, consider:
#!/bin/sh
# ^^ - because this code doesn't use any shell-specific extensions;
# use #!/bin/zsh if zsh-only, #!/bin/bash if bash-only, etc.
printf '%b' "\033[40m "
The above will behave identically, whether invoked with . yourscript, ./yourscript, bash yourscript, sh yourscript or zsh yourscript, because it's entirely well-defined by the POSIX specification (and doesn't rely on any of those behaviors for which zsh has chosen to break that specification).
If instead you were using extensions specific to a shell, it would need to be invoked either with ./yourscript (to honor the first line to select a shell according to its contents), with . yourscript from that same shell only, or with <shellname> yourscript, again, with the specific shell.
For this reason, the ./yourscript usage is strongly preferred, as it lets the script itself control its own interpreter.
Quoting again from the POSIX echo spec, this time from the APPLICATION USAGE section:
It is not possible to use echo portably across all POSIX systems unless both -n (as the first argument) and escape sequences are omitted.
The printf utility can be used portably to emulate any of the traditional behaviors of the echo utility as follows (assuming that IFS has its standard value or is unset):
The historic System V echo and the requirements on XSI implementations in this volume of IEEE Std 1003.1-2001 are equivalent to:
printf "%b\n" "$*"
The BSD echo is equivalent to:
if [ "X$1" = "X-n" ]
then
shift
printf "%s" "$*"
else
printf "%s\n" "$*"
fi
New applications are encouraged to use printf instead of echo.

Applescript does not execute shell command

I have an applescript
do shell script "echo -n -e \\\\x61\\\\x61\\\\x61 > /tmp/file.txt"
But the file.txt does not contain "aaa"!
It contains "-n -e aaa\n" instead.
Can someone help me with that problem?
Different versions of echo are hopelessly inconsistent in how they interpret command options (like -n and -e) and/or escape sequences in the string. It's not just bash vs. sh as cdarke said; it's much messier than that. The best thing to do is just avoid either one by using printf instead. It's a bit more complicated to use than echo, but completely worth it because your scripts won't break just because the latest version of the shell was compiled with different options(!).
In this case, using printf is actually even simpler than using echo, because it always interprets escape sequences (in its first argument, the "format string" -- the rest are different), and doesn't print a newline at the end (unless you explicitly tell it to with \n at the end of the format string). So your script becomes:
do shell script "printf \\\\x61\\\\x61\\\\x61 > /tmp/file.txt"
...although you can simplify it further by using single-quotes to keep the shell from interpreting escapes before they get to printf:
do shell script "printf '\\x61\\x61\\x61' > /tmp/file.txt"
(The escapes are still doubled, because they're being interpreted by AppleScript. But at least they don't need to be quadrupled anymore.)
(p.s. relevant xkcd)

echo newline character output

I'm writing a bash script and when I write the line:
echo "l=l.split('\n')"
I would like the output to actually be l=l.split('\n') but get:
l=l.split('
')
Any idea on how to fix this? I tried using quotations at different spots and escaping the characters differently but nothing seems to be working. Appreciate the help!
**Worth noting - if I simply type the echo command into the terminal I get my desired output.. Not sure why a script is treated differently.
It sounds like perhaps you got the shebang wrong (for a bash script, anyway). Take for example:
$ cat test.sh
#!/bin/sh
echo "l=l.split('\n')"
$ ./test.sh
l=l.split('
')
$ cat test.bash
#!/bin/bash
echo "l=l.split('\n')"
$ ./test.bash
l=l.split('\n')
Even though bash and sh may be provided by the same shell on some systems, there are subtle differences in their behavior. If you want it to behave like it does for you in a terminal, be sure to use #!/bin/bash.
tl;dr
If your script is really being run by bash, then something must have turned on the off-by-default xpg_echo shell option - either directly, or indirectly via shell option posix.
posix is implicitly turned on when Bash is invoked as sh (/bin/sh), which happens on macOS, for instance.
If your script is not being run by bash, which is the likeliest explanation, as suggested in FatalError's helpful answer:
Ensure that your script's shebang line is either
#!/bin/bash or #!/usr/bin/env bash.
Alternatively, pass it directly to bash: bash <script>
The portable solution, which shields you from variable echo behavior, is to use printf as follows:
printf '%s\n' "l=l.split('\n')"
Optional background information
In bash, the interpretation of escape sequences such as \n by echo is turned OFF by default.
Option xpg_echo must be turned on for escape sequences to be recognized by echo.
This option is implicitly on if you run bash in POSIX compatibility mode (verify with shopt -o posix), which also happens if you run Bash as sh, as is the case on macOS, for instance, where /bin/sh is Bash.
Note that irrespective of the state of xpg_echo by itself, you can opt into escape-sequence interpretation ad-hoc with echo -e and opt out with echo -E.
However, this does not work when running in POSIX compatibility mode (shopt -o posix), where - in compliance with POSIX - echo supports no options at all.
In other words: The following would only work if (a) your script is really being executed by bash and (b) option posix is off:
echo -E "l=l.split('\n')"
Again, printf is the portable, POSIX-compliant alternative that works consistently across all POSIX-compatible shells, irrespective of the state of shell options:
# '%s\n': *without* escape-sequence interpretation
$ printf '%s\n' "l=l.split('\n')"
l=l.split('\n')
# '%b\n': *with* escape-sequence interpretation
$ printf '%b\n' "l=l.split('\n')"
l=l.split('
')
This solves the problem.
echo "l=l.split('\\\n')"

How display color message from bash script?

I found this example:
echo -e "This is red->\e[00;31mRED\e[00m"
It works if execute direct, from command line, bu if create file like:
#! /usr/bin/sh
echo -e "This is red->\e[00;31mRED\e[00m"
Doesn't work. How to fix? Or may be possible output in bold?
Please don't use Lua it doesn't installed.
Edit This might be your problem (likely):
#!/bin/bash
echo -e "This is red->\e[00;31mRED\e[00m"
The reason is that sh doesn't have a builtin echo command, that supports escapes.
Alternatively you might invoke your script like
bash ./myscript.sh
Backgrounders
ANSI escape sequences are interpreted by the terminal.
If you run in a pipe/with IO redirected, ouput won't be to a terminal, hence the escapes don't get interpreted.
Hints:
see ansifilter for a tool that can filter ANSI escape sequences (and optionally translate to HTML and others)
use GNU less, e.g. to get ANSI escapes working in a pager:
grep something --colour=always files.* | less -R
Or simply, as I do
# also prevent wrapping long lines
alias less='less -SR'
Use an echo program, not an echo built-in command:
#!/bin/sh
MYECHO="`which echo`"
if <test-whether-MYECHO-empty-and-act-accordingly> ...
...
$MYCHO -e "This is red->\e[00;31mRED\e[00m"

Resources