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.
Answer from that other guy on Stack OverflowIn 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?
$foodoes not mean “take the value of the variablefoo”. 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 listfoo,*,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 finallybar. If the current directory is empty, the result isfoo,*,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"if there are three positional parameters. See What is the difference between2" "$3"
@?)
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
IFSdoesn'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.
What does the echo command do in Linux?
What is the difference between `echo` and `printf`?
Why does `echo` behave differently on macOS?
Videos
This is a very basic question, but I am new to bash scripting. In the code snippet below, I am trying to extract something from a file and save it to a variable. What I have shown in the first echo command is working correctly. Instead of echoing it, I want to save it to a variable. The stuff below that is not working, I feel like maybe I am missing brackets or have the $ in the wrong place. Thanks in advance for your help! Sorry about the bad formatting, I am posting from my phone.
!/bin/bash
header_file="../../build.vivado/output/headers/axi_gp_header.vh"
Extract the 3rd word (reg value) from the line that contains the word "DIAGNOSTICS":
echo $(grep "DIAGNOSTICS" $header_file) | cut -d " " -f 3
This does not print the same thing as above:
REG_VALUE=$((grep "DIAGNOSTICS" $header_file) | cut -d " " -f 3) echo $REG_VALUE
I have a bash script that connects to a Cisco router using telnet and sends a command. i want to capture the output of the command into a variable that i can referrence to in the script. below is the script and how it looks when i run it, i want to capture the out of the clock, or if that is not possible, can i capture the whole output of the telnet to a variable?
#!/bin/bash
Coonect via telnet and Send command a command
(
echo open “10.0.1.1”
sleep 2
echo “cisco”
echo “cisco”
echo “enable”
sleep 2
echo “cisco”
echo “show clock” # This is the command
sleep 2
) | telnet
[ Wrote 19 lines ]
[admin@BIG-IP-01:Active:Standalone] ~ #
[admin@BIG-IP-01:Active:Standalone] ~ #
[admin@BIG-IP-01:Active:Standalone] ~ # ./test
telnet> Trying 10.0.1.1…
Connected to 10.0.1.1.
Escape character is ‘^]’.
User Access Verification
Username: cisco
Password:
Server_1>enable
Password:
Server_1#show clock
*12:03:00.919 UTC Fri Feb 10 2023
Server_1#Connection closed by foreign host.
[admin@BIG-IP-01:Active:Standalone] ~ #
2 ideas for you to explore
-
If you don’t need to retain old data, you can set the commands to the variable rather than the output.
-
If you need keep the data so it doesn’t change on you, save it to a temp file, then you can save the contents of the temp file to your variable.