Why is echo producing a newline? - bash

What's the difference between
echo -n "
> "
and
echo -n ""
The first one produces a newline whereas the second doesn't.
I'm running GNU bash, version 4.2.45(1)-release (x86_64-pc-linux-gnu)
Edit : I get that the input gets a newline here. I should have been clearer with the question. Consider the following script input.sh
#!/bin/bash
echo -n $1
The following doesn't produce a newline.
./input.sh "
> "

The string
"
> "
has a newline in it as far as bash is concerned. The -n flag just means that echo will not print an extra newline at the end of your output. It will still reproduce your input, including newlines.

Expanding on #chepner's comment. Consider this:
$ set -- "
"
$ echo -n "$1" | od -c
0000000 \n
0000001
$ echo -n $1 | od -c
0000000
When you leave the variable unquoted, any leading or trailing sequences of whitespace are removed by shell. So bash discards your newline when you don't quote $1. This happens before "echo" is invoked, so "echo -n" is given no arguments.
From the Word Splitting section in the manual:
If IFS is unset, or its value is exactly <space><tab><newline>, the default, then sequences of <space>, <tab>, and <newline> at the beginning and end of the results of the previous expansions are ignored, and any sequence of IFS characters not at the beginning or end serves to delimit words.

Related

echo program prints newline instead of '\n'

In bash, anything between single quotes will remain its literal meaning, but when I type "echo '\n'" in my terminal. The output I get is a newline instead of the expected '\n' character.
According to the POSIX spec for echo:
If the first operand is -n, or if any of the operands contain a <backslash> character, the results are implementation-defined.
echo is normally a shell builtin, and zsh's will interpret backlash escapes by default. You can use echo -E, which tells both the zsh and bash versions of echo to disable treating them specially. zsh also uses the BSD_ECHO shell option to control this behavior:
$ echo "\n"
$ echo -E "\n"
\n
$ setopt BSD_ECHO
$ echo "\n"
\n
The most portable alternative, though, is using printf instead of echo, as it doesn't rely on any shell-specific behaviors:
$ printf '\\n\n'
\n
Checked this using bash and zsh under macos Catalina.
bash: $echo '\n’ produced "\n"
zsh: %echo '\n' produced newline
zsh: %echo '\n' produces "\n"

What's the difference between grep \\$ and grep \\\$?

When I type grep \\\$ shell escapes both \ and $ and transforms it to \$ and sends to grep, which then finds all the lines with dollar sign $. That's fine!
When I type grep \\$ the result is the same and I don't really know why. The first backslash should escape the second one, but then $ is not escaped and shell should replace it with an empty string? grep should receive \ and report an error but instead everything works as in first example for some reason..
In UNIX shells, $x is replaced by the value of the shell variable x but when there is nothing following the $, no substitution is performed. You can test this with echo:
> echo $
$
> echo $x
>
Your two grep arguments are passed into grep as exactly the same regular expression.
> echo \\\$
\$
> echo \\$
\$
>

What's the difference between <<EOF and <<\EOF heredocs in shell

I have noticed several distinction between them:
Inside a <<EOF heredoc, new values can not be assigned to variables:
bash <<EOF
s=fds
echo $s
EOF
will print empty line, where
bash <<\EOF
s=fds
echo $s
EOF
will print the value of the variable s.
Global variables can be accessed within <<EOF but not within <<\EOF (with export it is possible to access variables inside <<\EOF):
s=fds
bash <<EOF
echo $s
EOF
will print the value fds, where,
s=fds
bash <<\EOF
echo $s
EOF
will print empty line.
So what are the differences between them and what is the legitimate documented behavior?
From the POSIX spec:
If any character in word is quoted, the delimiter shall be formed by performing quote removal on word, and the here-document lines shall not be expanded. Otherwise, the delimiter shall be the word itself.
So the <<EOF version has the shell expand all variables before running the here doc contents and the <<\EOF (or <<'EOF' or <<EO'F' etc.) versions don't expand the contents (which lets bash in this case do that work).
Try it with cat instead of bash for a clearer view on what is happening.
Also with printf '[%s]\n' "$s" and/or possibly bash -x instead of bash:
$ bash -x <<EOF
s=fds
printf '[%s]\n' "$s"
EOF
+ s=fds
+ printf '[%s]\n' ''
[]
$ bash -x <<\EOF
s=fds
printf '[%s]\n' "$s"
EOF
+ s=fds
+ printf '[%s]\n' fds
[fds]
Documentation: http://www.gnu.org/software/bash/manual/bash.html#Here-Documents
In your first example the delimiter is unquoted, so variable expansion occurs and it's like you're running the code
echo "s=fds
echo $s" | bash
which expands $s in the current shell, where it's empty. So the new shell sees
s=fds
echo
Read the Advanced Bash Scripting Guide & bash reference manual in particular about redirections:
The format of here-documents is:
<<[-]word
here-document
delimiter
No parameter and variable expansion, command substitution, arithmetic
expansion, or filename expansion is performed on word. If any
characters in word are quoted, the delimiter is the result of quote
removal on word, and the lines in the here-document are not expanded.
If word is unquoted, all lines of the here-document are subjected to
parameter expansion, command substitution, and arithmetic expansion,
the character sequence \newline is ignored, and ‘\’ must be used to
quote the characters ‘\’, ‘$’, and ‘`’.

Understanding bash echo

I'm not understanding bash's echo command. Can you explain why echo $a just outputs a newline in the following?
MacbookAir1:so1 palfvin$ a='[]'
MacbookAir1:so1 palfvin$ echo $a
MacbookAir1:so1 palfvin$ echo "$a"
[]
MacbookAir1:so1 palfvin$ echo '[]'
[]
MacbookAir1:so1 palfvin$
I'd particularly appreciate a reference to some documentation explaining this.
The bash version is GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
Update: Output of examination of IFS after resetting per comment thread, is as follows:
MacbookAir1:~ palfvin$ echo "$IFS" | od -c
0000000 \t \n \n
0000004
MacbookAir1:~ palfvin$ echo "$IFS" | od -h
0000000 0920 0a0a
0000004
Having the nullglob shell option set can cause this. [] is a glob pattern that matches any of the characters between [ and ], which is to say nothing. Normally, if the shell finds a pattern that doesn't match anything, it just leaves it alone; but with nullglob it's removed :
$ a='[]'
$ echo $a
[]
$ shopt -s nullglob
$ echo $a
$
From the Filename Expansion section of the bash manual:
After word splitting, unless the -f option has been set (see The Set
Builtin), Bash scans each word for the characters ‘*’, ‘?’, and ‘[’.
If one of these characters appears, then the word is regarded as a
pattern, and replaced with an alphabetically sorted list of file names
matching the pattern. If no matching file names are found, and the
shell option nullglob is disabled, the word is left unchanged. If the
nullglob option is set, and no matches are found, the word is removed.
What's your $IFS set to?
When I set it to IFS='[]', then I can reproduce your result (on a MacBook Pro running Mavericks 10.9.1 and using Bash 3.2.51(1)). When IFS is set to spaces, the problem does not reproduce:
$ echo []
[]
$ x=[]
$ echo $x
[]
$ IFS='[]'
$ echo $x
$
Incidentally, bash -v invokes verbose mode; it echoes the shell script as it processes it. You might still be in a sub-shell, which shouldn't matter much until you try to exit from it, when you'll find yourself in a long-forgotten parent shell instead of having a closed window.

How to preserve newline characters in grep result?

I want to assign the grep result to a variable for further use:
lines=$(cat abc.txt | grep "hello")
but seems newline characters are removed in the result, when I do
echo $lines
only one line is printed. How can I preserve newline characters, so when I echo $lines, it generates the same result as cat abc.txt | grep "hello" does.
You want to say
echo "$lines"
instead of
echo $lines
To elaborate:
echo $lines means "Form a new command by replacing $lines with the contents of the variable named lines, splitting it up on whitespace to form zero or more new arguments to the echo command. For example:
lines='1 2 3'
echo $lines # equivalent to "echo 1 2 3"
lines='1 2 3'
echo $lines # also equivalent to "echo 1 2 3"
lines="1
2
3"
echo $lines # also equivalent to "echo 1 2 3"
All these examples are equivalent, because the shell ignores the specific kind of whitespace between the individual words stored in the variable lines. Actually, to be more precise, the shell splits the contents of the variable on the characters of the special IFS (Internal Field Separator) variable, which defaults (at least on my version of bash) to the three characters space, tab, and newline.
echo "$lines", on the other hand, means to form a single new argument from the exact value of the variable lines.
For more details, see the "Expansion" and "Word Splitting" sections of the bash manual page.
Using the Windows port for grep (not the original question, I kow and not applicable to *nix). I found that -U solved the problem nicely.
From the --help:
-U, --binary do not strip CR characters at EOL (MSDOS)

Resources