echo output different answer by sh and bash
echo
is not very portable (even Bash's echo
may behave differently on different OSes which may use different default options when compiling Bash). You can use printf
. According to posix:
It is not possible to use
echo
portably across all POSIX systems unless both-n
(as the first argument) and escape sequences are omitted.
Theprintf
utility can be used portably to emulate any of the traditional behaviors of theecho
utility [...]
Bash/SH, Same command different output?
Per POSIX, echo
supports no options.
Therefore, when echo -n
is run with sh
, it outputs literal -n
instead of interpreting -n
as the no-trailing-newline option:
$ sh -c 'echo -n "apple"'
-n apple # !! Note the -n at the beginning.
Note: Not all sh
implementations behave this way; some, such as on Ubuntu (where dash
acts as sh
), do support the -n
option, but the point is that you cannot rely on that, if your code must run on multiple platforms.
The portable POSIX-compliant way to print to stdout is to use the printf
utility:
printf %s "apple" | shasum -a 256
echo $(command) gets a different result with the output of the command
The command substitution is executed by your local shell before ssh
runs.
If your local system's name is here
and the remote is there
,
ssh there uname -n
will print there
whereas
ssh there echo $(uname -n) # should have proper quoting, too
will run uname -n
locally and then send the expanded command line echo here
to there
to be executed.
As an additional aside, echo $(command)
is a useless use of echo
unless you specifically require the shell to perform wildcard expansion and whitespace tokenization on the output of command
before printing it.
Also, grep x | awk { y }
is a useless use of grep
; it can and probably should be refactored to awk '/x/ { y }'
-- but of course, here you are reinventing pidof
so better just use that.
ssh user@myserver.com pidof /srv/adih/server/app.js
If you want to capture the printed PID locally, the syntax for that is
pid=$(ssh user@myserver.com pidof /srv/adih/server/app.js)
Of course, if you only need to kill it, that's
ssh user@myserver.com pkill /srv/adih/server/app.js
Why This Command's Output Is Different Between Sh and Bash?
&>
is not a valid redirection in sh. &
sends the command xset q
to background use if ! xset q >/dev/null 2>&1;then echo 'test'; fi;
bash vs sh script different results
Different versions of echo
do different things when given -n
as their first argument. Some print it as part of their output, some interpret it as a flag, who knows what's going to happen. According to the POSIX standard for echo
, "If the first operand is -n
, or if any of the operands contain a backslash character, the results are implementation-defined."
The most reliable way to print a string without a linefeed after it is with printf
. It's slightly more complicated because you have to give it a format string as well as the string you want printed:
hex=$(printf "%s" 'betty' | xxd -p)
Why does my Bash code fail when I run it with 'sh'?
TL;DR: Since you are using Bash specific features, your script has to run with Bash and not with sh
:
$ sh myscript.sh
myscript.sh: 2: myscript.sh: Bad substitution
$ bash myscript.sh
ffmpeg -i bar.mp4 bar.mp3
ffmpeg -i foo.mp4 foo.mp3
See Difference between sh and Bash. To find out which sh you are using: readlink -f $(which sh)
.
The best way to ensure a bash specific script always runs correctly
The best practices are to both:
- Replace
#!/bin/sh
with#!/bin/bash
(or whichever other shell your script depends on). - Run this script (and all others!) with
./myscript.sh
or/path/to/myscript.sh
, without a leadingsh
orbash
.
Here's an example:
$ cat myscript.sh
#!/bin/bash
for i in *.mp4
do
echo ffmpeg -i "$i" "${i/.mp4/.mp3}"
done
$ chmod +x myscript.sh # Ensure script is executable
$ ./myscript.sh
ffmpeg -i bar.mp4 bar.mp3
ffmpeg -i foo.mp4 foo.mp3
(Related: Why ./ in front of scripts?)
The meaning of #!/bin/sh
The shebang suggests which shell the system should use to run a script. This allows you to specify #!/usr/bin/python
or #!/bin/bash
so that you don't have to remember which script is written in what language.
People use #!/bin/sh
when they only use a limited set of features (defined by the POSIX standard) for maximum portability. #!/bin/bash
is perfectly fine for user scripts that take advantage of useful bash extensions.
/bin/sh
is usually symlinked to either a minimal POSIX compliant shell or to a standard shell (e.g. bash). Even in the latter case, #!/bin/sh
may fail because bash
will run in compatibility mode as explained in the man page:
If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.
The meaning of sh myscript.sh
The shebang is only used when you run ./myscript.sh
, /path/to/myscript.sh
, or when you drop the extension, put the script in a directory in your $PATH
, and just run myscript
.
If you explicitly specify an interpreter, that interpreter will be used. sh myscript.sh
will force it to run with sh
, no matter what the shebang says. This is why changing the shebang is not enough by itself.
You should always run the script with its preferred interpreter, so prefer ./myscript.sh
or similar whenever you execute any script.
Other suggested changes to your script:
- It is considered good practice to quote variables (
"$i"
instead of$i
). Quoted variables will prevent problems if the stored file name contains white space characters. - I like that you use advanced parameter expansion. I suggest to use
"${i%.mp4}.mp3"
(instead of"${i/.mp4/.mp3}"
), since${parameter%word}
only substitutes at the end (for example a file namedfoo.mp4.backup
).
How to echo shell commands as they are executed
set -x
or set -o xtrace
expands variables and prints a little + sign before the line.
set -v
or set -o verbose
does not expand the variables before printing.
Use set +x
and set +v
to turn off the above settings.
On the first line of the script, one can put #!/bin/sh -x
(or -v
) to have the same effect as set -x
(or -v
) later in the script.
The above also works with /bin/sh
.
See the bash-hackers' wiki on set
attributes, and on debugging.
$ cat shl
#!/bin/bash
DIR=/tmp/so
ls $DIR
$ bash -x shl
+ DIR=/tmp/so
+ ls /tmp/so
$
Redirect echo output in shell script to logfile
You can add this line on top of your script:
#!/bin/bash
# redirect stdout/stderr to a file
exec >logfile.txt 2>&1
OR else to redirect only stdout use:
exec > logfile.txt
Related Topics
Script to Get User That Has Process with Most Memory Usage
Replace Line with Space and Backslash with a String Containing Spaces
"Bad Interpreter" Error Message When Trying to Run Awk Executable
Why This Bash Function Prints Only First Word of Whole String
Escaping Single Quotes in Shell for Postgresql
How to Stop Apache from Listing the Contents of My User Directories
Top Command Output Is Empty When Run from Cron
How to Run a Cron Job at Every 5 Hour Interval
Simple Way to Get Filesize in X86 Assembly Language
Making Proprietary Elf Binaries Portable on Linux
How to Offload the 1Hz Tick in Dyntick Mode
Stop Being Root in the Middle of a Script That Was Run with Sudo
Rsync, 'Uid/Gid Impossible to Set' Cases Cause Future Hard Link Failure, How to Fix
Will Data Written via Write() Be Flushed to Disk If a Process Is Killed
How to Merge Similar Lines in Linux
How to Check If a Process Is Still Running from the Kernel in C