How to read mutliline input from stdin into variable and how to print one out in shell(sh,bash)?

BashShellStdinMultiline

Bash Problem Overview


What I want to do is the following:

  1. read in multiple line input from stdin into variable A
  2. make various operations on A
  3. pipe A without losing delimiter symbols (\n,\r,\t,etc) to another command

The current problem is that, I can't read it in with read command, because it stops reading at newline.

I can read stdin with cat, like this:

my_var=`cat /dev/stdin`

, but then I don't know how to print it. So that the newline, tab, and other delimiters are still there.

My sample script looks like this:

#!/usr/local/bin/bash

A=`cat /dev/stdin`

if [ ${#A} -eq 0 ]; then
        exit 0
else
        cat ${A} | /usr/local/sbin/nextcommand
fi

Bash Solutions


Solution 1 - Bash

This is working for me:

myvar=`cat`

echo "$myvar"

The quotes around $myvar are important.

Solution 2 - Bash

In Bash, there's an alternative way; man bash mentions:

> The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

$ myVar=$(</dev/stdin)
hello
this is test
$ echo "$myVar"
hello
this is test

Solution 3 - Bash

tee does the job

#!/bin/bash
myVar=$(tee)

Solution 4 - Bash

[updated]

This assignment will hang indefinitely if there is nothing in the pipe...

var="$(< /dev/stdin)"

We can prevent this though by doing a timeout read for the first character. If it times out, the return code will be greater than 128 and we'll know the STDIN pipe (a.k.a /dev/stdin) is empty.

Otherwise, we get the rest of STDIN by...

  • setting IFS to NULL for just the read command
  • turning off escapes with -r
  • eliminating read's delimiter with -d ''.
  • and finally, appending that to the character we got initially

Thus...

__=""
_stdin=""

read -N1 -t1 __  && {
  (( $? <= 128 ))  && {
    IFS= read -rd '' _stdin
    _stdin="$__$_stdin"
  }
}

This technique avoids using var="$(command ...)" Command Substitution which, by design, will always strip off any trailing newlines.

If Command Substitution is preferred, to preserve trailing newlines we can append one or more delimiter characters to the output inside the $() and then strip them off outside.

For example ( note $(parens) in first command and ${braces} in second )...

_stdin="$(awk '{print}; END {print "|||"}' /dev/stdin)"
_stdin="${_stdin%|||}"

Solution 5 - Bash

Yes it works for me too. Thanks.

myvar=`cat`

is the same as

myvar=`cat /dev/stdin`

Well yes. From the bash man page:

> Enclosing characters in double quotes > preserves the literal value of all > characters within the quotes, > with the exception of $, `, \, and, when history expansion is > enabled, !. The characters $ and ` > retain their special meaning within double quotes.

Solution 6 - Bash

If you do care about preserving trailing newlines at the end of the output, use this:

myVar=$(cat; echo x)
myVar=${myVar%x}
printf %s "$myVar"

This uses the trick from here.

Solution 7 - Bash

Read can also be used setting option -d [DELIMITER], in which `input' DELIMITER is one character long. If you set -read d '' then it reads until null or all input.

Just remember Bash cannot hold null bytes in variables, anyways.

read -d'' myvar
echo "$myvar"

Obs: trailing newline bytes are not preserved, though..

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkakasView Question on Stackoverflow
Solution 1 - BashTanktalusView Answer on Stackoverflow
Solution 2 - BashIngo KarkatView Answer on Stackoverflow
Solution 3 - BashSergey GrigorievView Answer on Stackoverflow
Solution 4 - BashDocSalvagerView Answer on Stackoverflow
Solution 5 - BashkakasView Answer on Stackoverflow
Solution 6 - BashIngo KarkatView Answer on Stackoverflow
Solution 7 - Bashuser11731610View Answer on Stackoverflow