String concatenation with "[" "]" results in 0 in shell script - linux

In the following code echo $d prints value 0
#!/bin/sh
printf "Enter the IP address: "
read server
a=":"
b="["
c="]"
d=$b$server$c
echo $d

You're already using printf. Use printf instead of echo. This works just fine:
printf "Enter the IP address: "
read server
a=":"
b="["
c="]"
d="${b}${server}${c}"
printf "%s\n" $d
Whenever you are having problems with echo, you can use printf just like the way I show it. In fact, I recommend that you use it instead of echo. Here's an example:
echo -n "Enter a string: "
read string
echo "$string"
Seems simple enough, but what if I entered -foo as my string? The echo will fail. However, this will work:
printf "%s" "Enter a string: "
read string
printf "%s\n" $string
No matter what you enter in for $string.
I did change the setting of $d by using quotes and curly braces. If you don't use quotes when setting d, and someone enters in this * is a string for your IP address, your program will expand * to match all of th files in the directory. Probably something you don't want. The curly braces just make it easier to see variables when they're all smushed together like that. Plus, it'll probably put each word on its own line.
Enter this * is a test as your IP address and try setting $d without quotes and then with quotes and see what happens.

If you echo a variable without quoting it, the contents are subject to, among other things, glob expansion. A glob consisting of open and close square brackets with something between them is seen by the shell as a pattern which is a choice of any of the characters within.
Here's a demonstration:
$ mkdir newdir
$ cd newdir
$ var='[192.168.0.1]'
$ echo $var # unquoted
[192.168.0.1]
$ touch 0
$ echo $var # unquoted
0
$ echo "$var" # quoted
[192.168.0.1]
As you can see, there is a file in the directory (after the touch command) called "0". There is a "0" inside the square brackets which matches. The sequence of characters inside the square brackets is simply that, not a string (when the variable is unquoted), but the duplicates are ignored. In this example, [192.680] is equivalent.
The lesson? You have a file named "0" in your directory. The solution? Always quote variables when their consumed.

Related

Bash: New line in echo string fails when output is piped to crontab [duplicate]

How do I print a newline? This merely prints \n:
$ echo -e "Hello,\nWorld!"
Hello,\nWorld!
Use printf instead:
printf "hello\nworld\n"
printf behaves more consistently across different environments than echo.
Make sure you are in Bash.
$ echo $0
bash
All these four ways work for me:
echo -e "Hello\nworld"
echo -e 'Hello\nworld'
echo Hello$'\n'world
echo Hello ; echo world
echo $'hello\nworld'
prints
hello
world
$'' strings use ANSI C Quoting:
Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard.
You could always do echo "".
For example,
echo "Hello,"
echo ""
echo "World!"
On the off chance that someone finds themselves beating their head against the wall trying to figure out why a coworker's script won't print newlines, look out for this:
#!/bin/bash
function GET_RECORDS()
{
echo -e "starting\n the process";
}
echo $(GET_RECORDS);
As in the above, the actual running of the method may itself be wrapped in an echo which supersedes any echos that may be in the method itself. Obviously, I watered this down for brevity. It was not so easy to spot!
You can then inform your comrades that a better way to execute functions would be like so:
#!/bin/bash
function GET_RECORDS()
{
echo -e "starting\n the process";
}
GET_RECORDS;
Simply type
echo
to get a new line
POSIX 7 on echo
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html
-e is not defined and backslashes are implementation defined:
If the first operand is -n, or if any of the operands contain a <backslash> character, the results are implementation-defined.
unless you have an optional XSI extension.
So I recommend that you should use printf instead, which is well specified:
format operand shall be used as the format string described in XBD File Format Notation [...]
the File Format Notation:
\n <newline> Move the printing position to the start of the next line.
Also keep in mind that Ubuntu 15.10 and most distros implement echo both as:
a Bash built-in: help echo
a standalone executable: which echo
which can lead to some confusion.
str='hello\nworld'
$ echo | sed "i$str"
hello
world
You can also do:
echo "hello
world"
This works both inside a script and from the command line.
On the command line, press Shift+Enter to do the line break inside the string.
This works for me on my macOS and my Ubuntu 18.04 (Bionic Beaver) system.
For only the question asked (not special characters etc) changing only double quotes to single quotes.
echo -e 'Hello,\nWorld!'
Results in:
Hello,
World!
There is a new parameter expansion added in Bash 4.4 that interprets escape sequences:
${parameter#operator} - E operator
The expansion is a string that is the value of parameter with
backslash escape sequences expanded as with the $'…' quoting
mechanism.
$ foo='hello\nworld'
$ echo "${foo#E}"
hello
world
I just use echo without any arguments:
echo "Hello"
echo
echo "World"
To print a new line with echo, use:
echo
or
echo -e '\n'
This could better be done as
x="\n"
echo -ne $x
-e option will interpret backslahes for the escape sequence
-n option will remove the trailing newline in the output
PS: the command echo has an effect of always including a trailing newline in the output so -n is required to turn that thing off (and make it less confusing)
My script:
echo "WARNINGS: $warningsFound WARNINGS FOUND:\n$warningStrings
Output:
WARNING : 2 WARNINGS FOUND:\nWarning, found the following local orphaned signature file:
On my Bash script I was getting mad as you until I've just tried:
echo "WARNING : $warningsFound WARNINGS FOUND:
$warningStrings"
Just hit Enter where you want to insert that jump. The output now is:
WARNING : 2 WARNINGS FOUND:
Warning, found the following local orphaned signature file:
If you're writing scripts and will be echoing newlines as part of other messages several times, a nice cross-platform solution is to put a literal newline in a variable like so:
newline='
'
echo "first line${newline}second line"
echo "Error: example error message n${newline}${usage}" >&2 #requires usage to be defined
If the previous answers don't work, and there is a need to get a return value from their function:
function foo()
{
local v="Dimi";
local s="";
.....
s+="Some message here $v $1\n"
.....
echo $s
}
r=$(foo "my message");
echo -e $r;
Only this trick worked on a Linux system I was working on with this Bash version:
GNU bash, version 2.2.25(1)-release (x86_64-redhat-linux-gnu)
You could also use echo with braces,
$ (echo hello; echo world)
hello
world
This got me there....
outstuff=RESOURCE_GROUP=[$RESOURCE_GROUP]\\nAKS_CLUSTER_NAME=[$AKS_CLUSTER_NAME]\\nREGION_NAME=[$REGION_NAME]\\nVERSION=[$VERSION]\\nSUBNET-ID=[$SUBNET_ID]
printf $outstuff
Yields:
RESOURCE_GROUP=[akswork-rg]
AKS_CLUSTER_NAME=[aksworkshop-804]
REGION_NAME=[eastus]
VERSION=[1.16.7]
SUBNET-ID=[/subscriptions/{subidhere}/resourceGroups/makeakswork-rg/providers/Microsoft.Network/virtualNetworks/aks-vnet/subnets/aks-subnet]
Sometimes you can pass multiple strings separated by a space and it will be interpreted as \n.
For example when using a shell script for multi-line notifcations:
#!/bin/bash
notify-send 'notification success' 'another line' 'time now '`date +"%s"`
With jq:
$ jq -nr '"Hello,\nWorld"'
Hello,
World
Additional solution:
In cases, you have to echo a multiline of the long contents (such as code/ configurations)
For example:
A Bash script to generate codes/ configurations
echo -e,
printf might have some limitation
You can use some special char as a placeholder as a line break (such as ~) and replace it after the file was created using tr:
echo ${content} | tr '~' '\n' > $targetFile
It needs to invoke another program (tr) which should be fine, IMO.

When should I use "" to quote a value in shell test and in echo?

I'm writing bash script like this:
VF_ETH=$(command)
if [ -n "$VF_ETH" ] ; then
echo "ixgbevf eth: "$VF_ETH
fi
command is a linux command, my question is:
$VF_ETH is to get value of VF_ETH, why use "" to quote it in line2 in shell test?
if I do not use "" to quote it, will test failed?
if use "" to quote a value is to make it into string, why not use in echo in line3?
Thank you
Assuming you get an actual command stored in VF_ETH variable, which contains spaces. Now if you use if [ -n $VF_ETH ] and when shell expands the variable, there will be multiple parameters to -n whereas it expects only one. Hence you might get something like binary operator expected error.
Also in the echo command, it is not mandatory to have only one parameter. Hence even if you are not using double quotes, it works.
Hence to avoid it, always use double quotes while expanding variables.
Also use https://www.shellcheck.net/ to check your script and it will give you correct information on where your script is wrong/not as per standard.
You should always double quote variables used in command line arguments and within [ ... ] tests, for example:
ls "$var"
echo "$var"
[ -f "$var" ]
test -f "$var"
In all the above examples the commands used will receive different values with and without the double quotes, when the value of $var starts with a bunch of spaces and contains some spaces. For example:
var=" an apple"
echo "$var" # prints " an apple"
echo $var # prints "an apple", without the leading space
You don't need to quote them in simple assignments, for example:
a=$var
a=$var$b
If the assignment contains spaces or other special characters, then you have to quote, or escape those special characters:
a="$var $b"
a=$var\ $b
a=$var" "$b
All the above are equivalent, but the first one is probably the easiest to read, and therefore recommended.
You don't need to quote special variables that never have unsafe values, for example:
test $? = 0
If you're unsure, or not yet confident, then a good role of thumb is to double quote always.
For 1. and 2. If you set $VF_ETH="x -a -z x" and test it with code:
if [ -n $VF_ETH ] ; then
echo yes
else
echo nope
fi
the output will be nope as the the inside of the square brackets would expand to -n x AND -z x (nonempty and empty). Adding quotes around the variable would fix that.
Set $VF_ETH="*". Now if you echo $foo bash would do wildcard expansion and echo would output the contents of your current directory.

Bash script printing [duplicate]

Here are a series of cases where echo $var can show a different value than what was just assigned. This happens regardless of whether the assigned value was "double quoted", 'single quoted' or unquoted.
How do I get the shell to set my variable correctly?
Asterisks
The expected output is /* Foobar is free software */, but instead I get a list of filenames:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Square brackets
The expected value is [a-z], but sometimes I get a single letter instead!
$ var=[a-z]
$ echo $var
c
Line feeds (newlines)
The expected value is a a list of separate lines, but instead all the values are on one line!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Multiple spaces
I expected a carefully aligned table header, but instead multiple spaces either disappear or are collapsed into one!
$ var=" title | count"
$ echo $var
title | count
Tabs
I expected two tab separated values, but instead I get two space separated values!
$ var=$'key\tvalue'
$ echo $var
key value
In all of the cases above, the variable is correctly set, but not correctly read! The right way is to use double quotes when referencing:
echo "$var"
This gives the expected value in all the examples given. Always quote variable references!
Why?
When a variable is unquoted, it will:
Undergo field splitting where the value is split into multiple words on whitespace (by default):
Before: /* Foobar is free software */
After: /*, Foobar, is, free, software, */
Each of these words will undergo pathname expansion, where patterns are expanded into matching files:
Before: /*
After: /bin, /boot, /dev, /etc, /home, ...
Finally, all the arguments are passed to echo, which writes them out separated by single spaces, giving
/bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
instead of the variable's value.
When the variable is quoted it will:
Be substituted for its value.
There is no step 2.
This is why you should always quote all variable references, unless you specifically require word splitting and pathname expansion. Tools like shellcheck are there to help, and will warn about missing quotes in all the cases above.
You may want to know why this is happening. Together with the great explanation by that other guy, find a reference of Why does my shell script choke on whitespace or other special characters? written by Gilles in Unix & Linux:
Why do I need to write "$foo"? What happens without the quotes?
$foo does not mean “take the value of the variable foo”. It means
something much more complex:
First, take the value of the variable.
Field splitting: treat that value as a whitespace-separated list of fields, and build the resulting list. For example, if the variable
contains foo * bar ​ then the result of this step is the 3-element
list foo, *, bar.
Filename generation: treat each field as a glob, i.e. as a wildcard pattern, and replace it by the list of file names that match this
pattern. If the pattern doesn't match any files, it is left
unmodified. In our example, this results in the list containing foo,
following by the list of files in the current directory, and finally
bar. If the current directory is empty, the result is foo, *,
bar.
Note that the result is a list of strings. There are two contexts in
shell syntax: list context and string context. Field splitting and
filename generation only happen in list context, but that's most of
the time. Double quotes delimit a string context: the whole
double-quoted string is a single string, not to be split. (Exception:
"$#" to expand to the list of positional parameters, e.g. "$#" is
equivalent to "$1" "$2" "$3" if there are three positional
parameters. See What is the difference between $* and $#?)
The same happens to command substitution with $(foo) or with
`foo`. On a side note, don't use `foo`: its quoting rules are
weird and non-portable, and all modern shells support $(foo) which
is absolutely equivalent except for having intuitive quoting rules.
The output of arithmetic substitution also undergoes the same
expansions, but that isn't normally a concern as it only contains
non-expandable characters (assuming IFS doesn't contain digits or
-).
See When is double-quoting necessary? for more details about the
cases when you can leave out the quotes.
Unless you mean for all this rigmarole to happen, just remember to
always use double quotes around variable and command substitutions. Do
take care: leaving out the quotes can lead not just to errors but to
security
holes.
In addition to other issues caused by failing to quote, -n and -e can be consumed by echo as arguments. (Only the former is legal per the POSIX spec for echo, but several common implementations violate the spec and consume -e as well).
To avoid this, use printf instead of echo when details matter.
Thus:
$ vars="-e -n -a"
$ echo $vars # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a
However, correct quoting won't always save you when using echo:
$ vars="-n"
$ echo "$vars"
$ ## not even an empty line was printed
...whereas it will save you with printf:
$ vars="-n"
$ printf '%s\n' "$vars"
-n
user double quote to get the exact value. like this:
echo "${var}"
and it will read your value correctly.
echo $var output highly depends on the value of IFS variable. By default it contains space, tab, and newline characters:
[ks#localhost ~]$ echo -n "$IFS" | cat -vte
^I$
This means that when shell is doing field splitting (or word splitting) it uses all these characters as word separators. This is what happens when referencing a variable without double quotes to echo it ($var) and thus expected output is altered.
One way to prevent word splitting (besides using double quotes) is to set IFS to null. See http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :
If the value of IFS is null, no field splitting shall be performed.
Setting to null means setting to empty
value:
IFS=
Test:
[ks#localhost ~]$ echo -n "$IFS" | cat -vte
^I$
[ks#localhost ~]$ var=$'key\nvalue'
[ks#localhost ~]$ echo $var
key value
[ks#localhost ~]$ IFS=
[ks#localhost ~]$ echo $var
key
value
[ks#localhost ~]$
The answer from ks1322 helped me to identify the issue while using docker-compose exec:
If you omit the -T flag, docker-compose exec add a special character that break output, we see b instead of 1b:
$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$
With -T flag, docker-compose exec works as expected:
$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b
Additional to putting the variable in quotation, one could also translate the output of the variable using tr and converting spaces to newlines.
$ echo $var | tr " " "\n"
foo
bar
baz
Although this is a little more convoluted, it does add more diversity with the output as you can substitute any character as the separator between array variables.

How to make printf in bash script with a variable which comes from txt text with NEW LINES [duplicate]

Here are a series of cases where echo $var can show a different value than what was just assigned. This happens regardless of whether the assigned value was "double quoted", 'single quoted' or unquoted.
How do I get the shell to set my variable correctly?
Asterisks
The expected output is /* Foobar is free software */, but instead I get a list of filenames:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Square brackets
The expected value is [a-z], but sometimes I get a single letter instead!
$ var=[a-z]
$ echo $var
c
Line feeds (newlines)
The expected value is a a list of separate lines, but instead all the values are on one line!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Multiple spaces
I expected a carefully aligned table header, but instead multiple spaces either disappear or are collapsed into one!
$ var=" title | count"
$ echo $var
title | count
Tabs
I expected two tab separated values, but instead I get two space separated values!
$ var=$'key\tvalue'
$ echo $var
key value
In all of the cases above, the variable is correctly set, but not correctly read! The right way is to use double quotes when referencing:
echo "$var"
This gives the expected value in all the examples given. Always quote variable references!
Why?
When a variable is unquoted, it will:
Undergo field splitting where the value is split into multiple words on whitespace (by default):
Before: /* Foobar is free software */
After: /*, Foobar, is, free, software, */
Each of these words will undergo pathname expansion, where patterns are expanded into matching files:
Before: /*
After: /bin, /boot, /dev, /etc, /home, ...
Finally, all the arguments are passed to echo, which writes them out separated by single spaces, giving
/bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
instead of the variable's value.
When the variable is quoted it will:
Be substituted for its value.
There is no step 2.
This is why you should always quote all variable references, unless you specifically require word splitting and pathname expansion. Tools like shellcheck are there to help, and will warn about missing quotes in all the cases above.
You may want to know why this is happening. Together with the great explanation by that other guy, find a reference of Why does my shell script choke on whitespace or other special characters? written by Gilles in Unix & Linux:
Why do I need to write "$foo"? What happens without the quotes?
$foo does not mean “take the value of the variable foo”. It means
something much more complex:
First, take the value of the variable.
Field splitting: treat that value as a whitespace-separated list of fields, and build the resulting list. For example, if the variable
contains foo * bar ​ then the result of this step is the 3-element
list foo, *, bar.
Filename generation: treat each field as a glob, i.e. as a wildcard pattern, and replace it by the list of file names that match this
pattern. If the pattern doesn't match any files, it is left
unmodified. In our example, this results in the list containing foo,
following by the list of files in the current directory, and finally
bar. If the current directory is empty, the result is foo, *,
bar.
Note that the result is a list of strings. There are two contexts in
shell syntax: list context and string context. Field splitting and
filename generation only happen in list context, but that's most of
the time. Double quotes delimit a string context: the whole
double-quoted string is a single string, not to be split. (Exception:
"$#" to expand to the list of positional parameters, e.g. "$#" is
equivalent to "$1" "$2" "$3" if there are three positional
parameters. See What is the difference between $* and $#?)
The same happens to command substitution with $(foo) or with
`foo`. On a side note, don't use `foo`: its quoting rules are
weird and non-portable, and all modern shells support $(foo) which
is absolutely equivalent except for having intuitive quoting rules.
The output of arithmetic substitution also undergoes the same
expansions, but that isn't normally a concern as it only contains
non-expandable characters (assuming IFS doesn't contain digits or
-).
See When is double-quoting necessary? for more details about the
cases when you can leave out the quotes.
Unless you mean for all this rigmarole to happen, just remember to
always use double quotes around variable and command substitutions. Do
take care: leaving out the quotes can lead not just to errors but to
security
holes.
In addition to other issues caused by failing to quote, -n and -e can be consumed by echo as arguments. (Only the former is legal per the POSIX spec for echo, but several common implementations violate the spec and consume -e as well).
To avoid this, use printf instead of echo when details matter.
Thus:
$ vars="-e -n -a"
$ echo $vars # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a
However, correct quoting won't always save you when using echo:
$ vars="-n"
$ echo "$vars"
$ ## not even an empty line was printed
...whereas it will save you with printf:
$ vars="-n"
$ printf '%s\n' "$vars"
-n
user double quote to get the exact value. like this:
echo "${var}"
and it will read your value correctly.
echo $var output highly depends on the value of IFS variable. By default it contains space, tab, and newline characters:
[ks#localhost ~]$ echo -n "$IFS" | cat -vte
^I$
This means that when shell is doing field splitting (or word splitting) it uses all these characters as word separators. This is what happens when referencing a variable without double quotes to echo it ($var) and thus expected output is altered.
One way to prevent word splitting (besides using double quotes) is to set IFS to null. See http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :
If the value of IFS is null, no field splitting shall be performed.
Setting to null means setting to empty
value:
IFS=
Test:
[ks#localhost ~]$ echo -n "$IFS" | cat -vte
^I$
[ks#localhost ~]$ var=$'key\nvalue'
[ks#localhost ~]$ echo $var
key value
[ks#localhost ~]$ IFS=
[ks#localhost ~]$ echo $var
key
value
[ks#localhost ~]$
The answer from ks1322 helped me to identify the issue while using docker-compose exec:
If you omit the -T flag, docker-compose exec add a special character that break output, we see b instead of 1b:
$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$
With -T flag, docker-compose exec works as expected:
$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b
Additional to putting the variable in quotation, one could also translate the output of the variable using tr and converting spaces to newlines.
$ echo $var | tr " " "\n"
foo
bar
baz
Although this is a little more convoluted, it does add more diversity with the output as you can substitute any character as the separator between array variables.

Escape special characters in echo

http://www.grymoire.com/Unix/Quote.html shows a list of special characters. Is there a parameter/option for echo where I can treat everything that comes after the echo as a string?
In python, i could use the """...""" or '''...'''.
$ python
>>> text = '''#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&'''
>>> print text
#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
I can do the same in unix's echo with ''' but not """, why is that so?
$ echo #"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
$ echo '''#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&'''
#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
$ echo """'''#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&"""
bash: !###%$#$^%$: event not found
What happens if i have a string like this?
#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&
How should I echo such a string? (the following command doesn't work)
echo '''#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&'''
Use printf:
$ printf "%s\n" $'#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&'
#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
$ printf "%s\n" $'#"`\|^!###%$#$^%\'\'\'$&%^*()?/\;:$#$"""#$?$$$!&&'
#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&
You might note that single quotes ' need to be escaped.
In order to assign the output to a variable:
$ foo=$(printf "%s\n" $'#"`\|^!###%$#$^%\'\'\'$&%^*()?/\;:$#$"""#$?$$$!&&')
$ echo $foo
#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&
It is because shell applies all expansion rules inside a string double quotes ". $ or ! are special Unix characters to denote variable or event hence you get that error.
I think it is because of your shell (bash), which expands/interprets double quotes.
This does not apply for single quotes.
For details, please have a look at Bash - Shell Expansion.
For the echo command there is the -e option which enables interpretation of backslash escapes - which might help.

Resources