684

I've got a script 'myscript' that outputs the following:

abc
def
ghi

in another script, I call:

declare RESULT=$(./myscript)

and $RESULT gets the value

abc def ghi

Is there a way to store the result either with the newlines, or with '\n' character so I can output it with 'echo -e'?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Parker
  • 7,949
  • 5
  • 26
  • 21
  • 2
    it surprises me. don't you have $(cat ./myscipt) ? otherwise i would have expected it to try to execute commands abc, def and ghi – Johannes Schaub - litb Mar 05 '09 at 04:35
  • @litb: yes, I suppose so; you can also use $(<./myscript) which avoids executing a command. – Jonathan Leffler Mar 05 '09 at 04:39
  • 3
    (NB: the two comments above refer to a revision of the question that started _I've got a script 'myscript' that contains the following_, which led to the questions. The current revision of the question (_I've got a script 'myscript' that outputs the following_) makes the comments superfluous. However, the revision is from 2011-11-11, long after the two comments were made. – Jonathan Leffler Dec 29 '13 at 17:53
  • for `$IFS` see [What is the exact meaning of `IFS=$'\n'`](https://stackoverflow.com/questions/4128235/what-is-the-exact-meaning-of-ifs-n/66942306#66942306) – Peyman Mohamadpour Apr 04 '21 at 21:49
  • Related: [Why do newline characters get lost when using command substitution?](https://unix.stackexchange.com/q/164508/80216),  [Why does my shell script choke on whitespace or other special characters?](https://unix.stackexchange.com/q/131766/80216)  (on Unix & Linux). – G-Man Says 'Reinstate Monica' Oct 04 '22 at 00:26

7 Answers7

1284

Actually, RESULT contains what you want — to demonstrate:

echo "$RESULT"

What you show is what you get from:

echo $RESULT

As noted in the comments, the difference is that (1) the double-quoted version of the variable (echo "$RESULT") preserves internal spacing of the value exactly as it is represented in the variable — newlines, tabs, multiple blanks and all — whereas (2) the unquoted version (echo $RESULT) replaces each sequence of one or more blanks, tabs and newlines with a single space. Thus (1) preserves the shape of the input variable, whereas (2) creates a potentially very long single line of output with 'words' separated by single spaces (where a 'word' is a sequence of non-whitespace characters; there needn't be any alphanumerics in any of the words).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 76
    @troelskn: the difference is that (1) the double-quoted version of the variable preserves internal spacing of the value exactly as it is represented in the variable, newlines, tabs, multiple blanks and all, whereas (2) the unquoted version replaces each sequence of one or more blanks, tabs and newlines with a single space. Thus (1) preserves the shape of the input variable, whereas (2) creates a potentially very long single line of output with 'words' separated by single spaces (where a 'word' is a sequence of non-whitespace characters; there needn't be any alphanumerics in any of the words). – Jonathan Leffler Apr 12 '11 at 01:03
  • 27
    To make the answer easier to understand: the answer tells that echo "$RESULT" preserves newline, while echo $RESULT does not. – Yu Shen Jul 14 '12 at 10:15
  • This fails to preserve newlines and leading spaces in some situations. – CommaToast Aug 13 '16 at 05:51
  • 4
    @CommaToast: Are you going to elaborate on that? Trailing newlines are lost; there isn't an easy way around that. Leading blanks — I'm not aware of any circumstances under which they're lost. – Jonathan Leffler Aug 13 '16 at 06:11
  • 2
    See also [When to wrap quotes around a shell variable?](http://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) – tripleee Mar 13 '17 at 09:39
  • As a supplement. Accoding to the **official BASH** manual, when `echo $RESULT` being used, [Word Splitting](https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html) happened, while [Quote Removal](https://www.gnu.org/software/bash/manual/html_node/Quote-Removal.html) happened for `echo "$RESULT"`. That's the exact reason why those two lead to the different behaviors. – Michael Lee May 25 '22 at 04:44
101

Another pitfall with this is that command substitution$() — strips trailing newlines. Probably not always important, but if you really want to preserve exactly what was output, you'll have to use another line and some quoting:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

This is especially important if you want to handle all possible filenames (to avoid undefined behavior like operating on the wrong file).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
l0b0
  • 55,365
  • 30
  • 138
  • 223
  • 6
    I had to work for a while with a broken shell that did not remove the last newline from the [command substitution](http://www.gnu.org/software/bash/manual/bash.html#Command-Substitution) (it is _not_ [process substitution](http://www.gnu.org/software/bash/manual/bash.html#Process-Substitution)), and it broke almost everything. For example, if you did ``pwd=`pwd`; ls $pwd/$file``, you got a newline before the `/`, and enclosing the name in double quotes didn't help. It was fixed quickly. This was back in 1983-5 time frame on ICL Perq PNX; the shell didn't have `$PWD` as a built-in variable. – Jonathan Leffler Dec 29 '13 at 17:59
29

In case that you're interested in specific lines, use a result-array:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"
user2574210
  • 323
  • 3
  • 2
  • 5
    If there are spaces in the lines, this will count fields (contents between spaces) rather than lines. – Liam Apr 15 '15 at 14:00
  • 4
    You would use `readarray` and process substitution instead of command substitution: `readarray -t RESULT < <(./myscript>`. – chepner Jan 18 '16 at 18:44
16

In addition to the answer given by @l0b0 I just had the situation where I needed to both keep any trailing newlines output by the script and check the script's return code. And the problem with l0b0's answer is that the 'echo x' was resetting $? back to zero... so I managed to come up with this very cunning solution:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Lurchman
  • 181
  • 1
  • 6
  • This is lovely, I actually had the use case where I want to have a `die ()` function that accepts an arbitrary exit code and optionally a message, and I wanted to use another `usage ()` function to supply the message but the newlines kept getting squashed, hopefully this lets me work around that. – dragon788 Sep 25 '18 at 22:43
7

Parsing multiple output

Introduction

So your myscript output 3 lines, could look like:

myscript() { echo $'abc\ndef\nghi'; }

or

myscript() { local i; for i in abc def ghi ;do echo $i; done ;}

Ok this is a function, not a script (no need of path ./), but output is same

myscript
abc
def
ghi

Considering result code

To check for result code, test function will become:

myscript() { local i;for i in abc def ghi ;do echo $i;done;return $((RANDOM%128));}

1. Storing multiple output in one single variable, showing newlines

Your operation is correct:

RESULT=$(myscript)

About result code, you could add:

RCODE=$?

even in same line:

RESULT=$(myscript) RCODE=$?

Then

echo $RESULT $RCODE
abc def ghi 66
echo "$RESULT"
abc
def
ghi
echo ${RESULT@Q}
$'abc\ndef\nghi'
printf '%q\n' "$RESULT"
$'abc\ndef\nghi'

but for showing variable definition, use declare -p:

declare -p RESULT RCODE
declare -- RESULT="abc
def
ghi"
declare -- RCODE="66"

2. Parsing multiple output in array, using mapfile

Storing answer into myvar variable:

mapfile -t myvar < <(myscript)
echo ${myvar[2]}
ghi

Showing $myvar:

declare -p myvar
declare -a myvar=([0]="abc" [1]="def" [2]="ghi")

Considering result code

In case you have to check for result code, you could:

RESULT=$(myscript) RCODE=$?
mapfile -t myvar <<<"$RESULT"

declare -p myvar RCODE
declare -a myvar=([0]="abc" [1]="def" [2]="ghi")
declare -- RCODE="40"

3. Parsing multiple output by consecutives read in command group

{ read firstline; read secondline; read thirdline;} < <(myscript)
echo $secondline
def

Showing variables:

declare -p firstline secondline thirdline
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"

I often use:

{ read foo;read foo total use free foo ;} < <(df -k /)

Then

declare -p use free total
declare -- use="843476"
declare -- free="582128"
declare -- total="1515376"

Considering result code

Same prepended step:

RESULT=$(myscript) RCODE=$?
{ read firstline; read secondline; read thirdline;} <<<"$RESULT"

declare -p firstline secondline thirdline RCODE
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"
declare -- RCODE="50"
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
1

After trying most of the solutions here, the easiest thing I found was the obvious - using a temp file. I'm not sure what you want to do with your multiple line output, but you can then deal with it line by line using read. About the only thing you can't really do is easily stick it all in the same variable, but for most practical purposes this is way easier to deal with.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Quick hack to make it do the requested action:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Note this adds an extra line. If you work on it you can code around it, I'm just too lazy.


EDIT: While this case works perfectly well, people reading this should be aware that you can easily squash your stdin inside the while loop, thus giving you a script that will run one line, clear stdin, and exit. Like ssh will do that I think? I just saw it recently, other code examples here: https://unix.stackexchange.com/questions/24260/reading-lines-from-a-file-with-bash-for-vs-while

One more time! This time with a different filehandle (stdin, stdout, stderr are 0-2, so we can use &3 or higher in bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

you can also use mktemp, but this is just a quick code example. Usage for mktemp looks like:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Then use $filenamevar like you would the actual name of a file. Probably doesn't need to be explained here but someone complained in the comments.

Community
  • 1
  • 1
user1279741
  • 127
  • 4
  • I tried other solutions too, with your first suggestion I finally got my script working – Kar.ma Sep 09 '16 at 15:04
  • 1
    Downvote: This is excessively complex, and fails to avoid multiple common [`bash` pitfalls](http://mywiki.wooledge.org/BashPitfalls). – tripleee Mar 13 '17 at 09:39
  • Ya someone told me about the weird stdin filehandle problem the other day and I was like "wow". Lemme add something really quickly. – user1279741 Mar 13 '17 at 20:21
  • 1
    In Bash, you can use the [`read`](https://www.gnu.org/software/bash/manual/bash.html#index-read) command extension `-u 3` to read from file descriptor 3. – Jonathan Leffler Aug 10 '18 at 16:41
  • In addition to @JonathanLeffler comment, you could write: `while read -ru $testout line;do echo "do something with $line";done {testout}< <(./test)` instead of creating temporary file! – F. Hauri - Give Up GitHub Oct 15 '20 at 08:46
0

How about this, it will read each line to a variable and that can be used subsequently ! say myscript output is redirected to a file called myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Rahul Reddy
  • 128
  • 10