I'm writing a script in bash and I want it to execute a command and to handle each line separately. for example:
LINES=$(df)
echo $LINES
it will return all the output converting new lines with spaces.
example:
if the output was supposed to be:
1
2
3
then I would get
1 2 3
how can I place the output of a command into a variable allowing new lines to still be new lines so when I print the variable i will get proper output?
Generally in bash $v is asking for trouble in most cases. Almost always what you really mean is "$v" in double quotes:
LINES="$(df)"
echo "$LINES"
No, it will not. The $(something) only strips trailing newlines.
The expansion in argument to echo splits on whitespace and than echo concatenates separate arguments with space. To preserve the whitespace, you need to quote again:
echo "$LINES"
Note, that the assignment does not need to be quoted; result of expansion is not word-split in assignment to variable and in argument to case. But it can be quoted and it's easier to just learn to just always put the quotes in.
Related
According to the Google Shell Style Guide, I should:
Always quote strings containing variables, command substitutions, spaces or shell meta characters, unless careful unquoted expansion is required.
Maybe I am misinterpreting what they mean by "command substitutions", but I am wondering if there is any need to use quotes in the following example:
VAR="$(echo foo bar)"
$(echo foo bar) is indeed a command substitution. In this specific example, you don't need double quotes because a variable assignment creates a “double quote context” for its right-hand side, so VAR=$(…) is equivalent to VAR="$(…)".
In bash, you don't need double quotes in export VAR=$(…) or declare VAR=$(…). But you do need the double quotes in some other sh implementations such as dash.
You do need double quotes in env VAR=$(…) somecommand, in make VAR=$(…), etc. It isn't the equal sign that makes the double quotes optional, it's the fact that the equal sign is parsed by the shell as an assignment.
There are a few other contexts where the double quotes are optional, but you can't go wrong with the simple rule: always use double quotes around variable and command substitutions unless you want the split+glob operator.
EDIT: (A command substitution is $( ) and the back-tick almost-equivalent, so you're likely interpreting it correctly)
It will certainly not hurt to quote strings in general in shell scripts, unless you're actually relying on globbing etc. to take effect.
In this simple example, the double-quotes are certainly not needed, but for consistency I would probably add them.
If you instead have
VAR=$( echo $foo $bar )
... then I would certainly quote both variables and expression:
VAR="$( echo "$foo" "$bar" )"
especially if any of those variable contained external input or if you knew they had globbing-characters in them.
EDIT: As user #CharlesDuffy points out, the outer double-quotes here are still not needed. I would still add them to be consistent with other variable assignments that do need quoting.
When not to quote a command substitution?
Answer: When you want the output of the command substitution to be treated as separate arguments.
Example:
We want to extract specific packets (packet numbers 1, 5, 10 and 20) from a .pcap file. The relevant command is the following:
editcap -r capture.pcap select.pcap 1 5 10 20
(Source to the command)
Now, we want to extract random packets from the file. To do that, we will use shuf from GNU Coreutils.
shuf -i 0-50 -n 4
8
24
20
31
The command above has generated 4 random numbers between 0 and 50.
Using it with editcap:
editcap -r source.pcap select.pcap "$(shuf -i 0-50 -n 4)"
editcap: The specified packet number "1
34
4
38" isn't a decimal number
As you can see, quoting the command substitution has resulted in the output string of the shuf to be treated as a single big string, including the newlines and whatnot as well. That is, putting quotes has resulted in the equivalent of the following command:
editcap -r source.pcap select.pcap "1
34
4
38"
Instead, we want Bash the chop the output of shuf and insert this chopped output as separate arguments to editcap. Omitting the quotes accomplishes that:
editcap -r source.pcap select.pcap $(shuf -i 0-50 -n 4)
quoting command substitutions realy avoid globbing expansion:
asterisk='*'
echo $(echo $asterisk)
echo $(echo "$asterisk")
echo "$(echo $asterisk)"
echo "$(echo "$asterisk")"
I want to list all the files in a directory one line after another, for which I am using a sample shell script as follows
#!/bin/sh
MY_VAR="$(ls -1)"
echo "$MY_VAR"
This works out as expected, however if the same is executed using csh as follows:
#!/bin/csh
set MY_VAR = `ls -1`
echo $MY_VAR
it outputs all files in one single line, than printing one file per line.
Can any one explain why in csh ls -1 is not working as expected.
From man csh, emphasis mine:
Command substitution
Command substitution is indicated by a command enclosed in ``'. The
output from such a command is broken into separate words at blanks,
tabs and newlines, and null words are discarded. The output is vari‐
able and command substituted and put in place of the original string.
Command substitutions inside double quotes (`"') retain blanks and
tabs; only newlines force new words. The single final newline does not
force a new word in any case. It is thus possible for a command sub‐
stitution to yield only part of a word, even if the command outputs a
complete line.
By default, the shell since version 6.12 replaces all newline and car‐
riage return characters in the command by spaces. If this is switched
off by unsetting csubstnonl, newlines separate commands as usual.
The entries are assigned in a list; you can access a single entry with e.g. echo $MY_VAR[2].
This is different from the Bourne shell, which doesn't have the concept of a "list" and variables are always strings.
To print all the entries on a single line, use a foreach loop:
#!/bin/csh
set my_var = "`ls -1`"
foreach e ($my_var)
echo "$e"
end
Adding double quotes around the `ls -1` is recommended, as this will make sure it works correctly when you have filenames with a space (otherwise such files would show up as two words/list entries).
I have very simple directory with "directory1" and "file2" in it.
After
out=`ls`
I want to print my variable: echo $out gives:
directory1 file2
but echo "$out" gives:
directory1
file2
so using quotes gives me output with each record on separate line. As we know ls command prints output using single line for all files/dirs (if line is big enough to contain output) so I expected that using double quotes prevents my shell from splitting words to separate lines while ommitting quotes would split them.
Pls tell me: why using quotes (used for prevent word-splitting) suddenly splits output ?
On Behavior Of ls
ls only prints multiple filenames on a single line by default when output is to a TTY. When output is to a pipeline, a file, or similar, then the default is to print one line to a file.
Quoting from the POSIX standard for ls, with emphasis added:
The default format shall be to list one entry per line to standard output; the exceptions are to terminals or when one of the -C, -m, or -x options is specified. If the output is to a terminal, the format is implementation-defined.
Literal Question (Re: Quoting)
It's the very act of splitting your command into separate arguments that causes it to be put on one line! Natively, your value spans multiple lines, so echoing it unmodified (without any splitting) prints it precisely that manner.
The result of your command is something like:
out='directory1
file2'
When you run echo "$out", that exact content is printed. When you run echo $out, by contrast, the behavior is akin to:
echo "directory1" "file2"
...in that the string is split into two elements, each passed as completely different argument to echo, for echo to deal with as it sees fit -- in this case, printing both those arguments on the same line.
On Side Effects Of Word Splitting
Word-splitting may look like it does what you want here, but that's often not the case! Consider some particular issues:
Word-splitting expands glob expressions: If a filename contains a * surrounded by whitespace, that * will be replaced with a list of files in the current directory, leading to duplicate results.
Word-splitting doesn't honor quotes or escaping: If a filename contains whitespace, that internal whitespace can't be distinguished from whitespace separating multiple names. This is closely related to the issues described in BashFAQ #50.
On Reading Directories
See Why you shouldn't parse the output of ls. In short -- in your example of out=`ls`, the out variable (being a string) isn't able to store all possible filenames in a useful, parsable manner.
Consider, for instance, a file created as such:
touch $'hello\nworld"three words here"'
...that filename contains spaces and newlines, and word-splitting won't correctly detect it as a single name in the output from ls. However, you can store and process it in an array:
# create an array of filenames
names=( * )
if ! [[ -e $names || -L $names ]]; then # this tests only the FIRST name
echo "No names matched" >&2 # ...but that's good enough.
else
echo "Found ${#files[#]} files" # print number of filenames
printf '- %q\n' "${names[#]}"
fi
I encountered this code:
file=$(<filename)
This reads the file from the filename.
My question is how does this work?
I read from this post:
How to use double or single brackets, parentheses, curly braces
It tells me, single parentheses can function as:
- Sub bash execution
- Array construction
But in the case above, I do not know how this explains.
Besides this question, I want to know that why when I do echo $file, the file content concatenate into one line?
$(...) performs command substitution; the command inside is read and the output from stdout is returned to the script.
<... is redirection; the contents of the file are read and fed into stdin of the process.
Putting the two together results in an implicit cat, connecting the stdin of the redirection to the stdout of the command substitution, reading the contents of the file into the script.
You must surround your variable into double quotes, else it will be expanded into command line arguments that will be passed to echo.
If you surround it into double quotes variable will be passed as single argument and echo will display it correctly.
echo "$file"
echo $file
will give you a concatenated output.
Try
echo "$file"
this will give you an output in multiple lines.
I would like to automate the following svn command. Note this command produces the desired results on my system - Ubuntu 10.04, svn 1.6.6, bash shell, when issued from the command line:
svn ci -m $'Added new File: newFile.txt\nOrig loc: /etc/networking/newFile.txt' /home/user/svnDir/newFile.txt
I would like to run that command in a bash script, assuming that the original full path to the file is contained in the variable $oFileFull, and the filename is in $oFileName. The script is executed from the svn directory. I need to allow for the possibility that the file name and or path contain spaces.
so the line inside my shel script might look like:
svn ci -m$'Added new file: ${oFileName}\nOrig loc: ${oFileFull}' ${oFileName}
But I want the variables (which may contain spaces) expanded before the command is executed, and I cannot figure out how to do this while enclosing the svn comment in single quotes which is necessary in order to get the new line in the subversion comment log. I am pulling my hair out trying to figure out how to properly quote and assemble this command. Any help appreciated.
The following was posted as an answer, but should have been an edit.
#Dennis:
Wow, this is hard to wrap my head around, I'd love it if someone could point me towards an online resource that explains this clearly and concisely. The ones I have found have not cleared it up for me.
My latest source of confusion is the following:
#!/bin/bash
nl=$'\n'
msg="Line 1${nl}Line 2"
echo $msg # ouput = Line 1 Line 2
echo -e $msg # ouput = Line 1 Line 2
echo "$msg" # output = Line 1
# Line 2
What I'm attempting to illustrate is that having the double quotes around the variable $msg splits the output into two lines, without the double quotes, even with the -e switch, there is no new line.
I don't get why the double quotes are necessary - why isn't the $nl variable expanded when it is assigned as part of the msg variable?
I hope I'm not committing a StackOverflow faux pas by answering my own question. And in fact, I am not really providing an answer, but merely a response to your comment. I cannot format a comment as necessary.
Put your newline in a variable, use the variable wherever you need a newline and change the quotes around your larger string to double quotes. You should also always quote any variable that contains a filename.
nl=$'\n'
svn ci -m"Added new file: ${oFileName}${nl}Orig loc: ${oFileFull}" "${oFileName}"
#Scott, I know this has already been answered, but here are some comments on your additional question about the difference between echo $msg and echo "$msg". Your example:
nl=$'\n'
msg="Line 1${nl}Line 2"
echo $msg # ouput = Line 1 Line 2
echo -e $msg # ouput = Line 1 Line 2
echo "$msg" # output = Line 1
# Line 2
It really helps to know how many arguments the shell is passing to the echo command in each case. Echo takes any number of arguments. It writes all of them to standard output, in order, with a single space after each one except the last one.
In the first example, echo $msg, the shell replaces $msg with the characters that you've set it to (one of which is a newline). Then before invoking echo it splits that string into multiple arguments using the Internal Field Separator (IFS), and it passes that resulting list of arguments to echo. By default, the IFS is set to whitespace (spaces, tabs, and newlines), so the shell chops your $msg string into four arguments: "Line", "1", "Line", and "2". Echo never even sees the newline, because the shell considers it a separator, just like a space.
In the second example, echo -e $msg, the echo command receives five arguments: "-e", "Line", "1", "Line", "2". The -e argument tells the command to expand any backslash characters that it sees in its arguments, but there are none, so echo leaves the arguments unchanged. The result is the same as the first example.
In the last example, echo "$msg", you're telling the shell to expand $msg and pass its contents to echo but to treat everything between the double quotation marks as one argument, so echo receives one argument, a string that contains letters, numbers, spaces and newlines. It echoes that string exactly as it receives it, so "Line 1" and "Line 2" appear on separate lines.
Try this experiment: set the IFS to something arbitrary, like the letter i, and watch how the shell splits your string differently:
nl=$'\n'
msg="Line 1${nl}Line 2"
IFS=i
echo $msg # ouput = L ne 1
# L ne 2
There's nothing especially magical about whitespace. Here, because of the weird IFS, the shell treats the letter i as a separator but not the spaces or newlines. So echo receives three arguments: "L", "ne \nL", and "ne 2". (I'm showing the newline character as \n, but it's really just a single character, like X or p or 5.)
Once you realize that single and double quotation marks on the shell's command line are all about constructing arguments for the commands, the logic begins to make sense.
Bash will concatenate adjacent quoted sections. So for example
$ r=rooty
$ t=tooty
$ echo $r$'\n'$t
rooty
tooty
$ echo "$r $r"$'\n'$t
rooty rooty
tooty
For an online guide, the advanced bash scripting guide may be useful, though it may be no better than just reading bash's long and detailed manual. I recommend using vim's Man command (standard under version 7.2, if not earlier versions) to bring up the bash manual, and then using indent-based folding so that you can browse its heirarchy. Well, assuming that you don't have the time and patience to read all 3300 lines from start to finish.