78

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
xpda
  • 15,585
  • 8
  • 51
  • 82

7 Answers7

85

This is working for me:

myvar=`cat`

echo "$myvar"

The quotes around $myvar are important.

OlivierBlanvillain
  • 7,701
  • 4
  • 32
  • 51
Tanktalus
  • 21,664
  • 5
  • 41
  • 68
35

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
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
  • -1 because `echo "$myVar"` will add a newline character if it did not already have one. – CommaToast Sep 08 '14 at 22:22
  • 2
    @CommaToast: And `$(...)` will remove any trailing newlines, so they fit perfectly together, don't they? And you can easily switch to `printf` to avoid that. – Ingo Karkat Sep 09 '14 at 06:43
  • 1
    No, they don't fit together perfectly. You will end up with a trailing newline no matter what in this solution, whether or not there was one to begin with. If you switch to printf arbitrarily, now you will never have a trailing newline, even if there was one. – CommaToast Sep 09 '14 at 06:47
  • @CommaToast: I agree that the behavior of `$(...)` isn't optimal. But there are ways around that, see [here](http://wiki.bash-hackers.org/syntax/expansion/cmdsubst#examples) – Ingo Karkat Sep 09 '14 at 06:52
  • 7
    Well to be fair it's not your fault that unix was designed in 1946 and is basically a glorified typewriter, as far as it's concerned. I can just see the carriage return going back and forth inside unix's brain. It just can't be asked to treat newlines like every other character. No, it just *has* to revert to its typewriter reptilian brain. But anyway the only workaround I could find to this is `var=\`cat; echo x\`` then later when ready to output it, output `${var%x}` so that newlines (or lack thereof) are preserved. Ugly as heck but, it works... – CommaToast Sep 09 '14 at 06:56
  • @CommaToast: Right. I've added this as an additional answer, for those cases where trailing newlines (or lack thereof) must be preserved. – Ingo Karkat Sep 09 '14 at 07:02
  • Nods. I want to make a version of Unix that has a completely separate character set for commands and programming compared to the character set used for comments and content. There'd be a second caps-lock button that would be "command-lock" and it would be cruise-control for win. That way, for example, a newline in the *command* set could always be respected, whereas a newline in the *content* would be ignored in situations like this. Of course, documents would need to be able to be switched so their command characters were made into content for purposes like git. – CommaToast Sep 09 '14 at 07:09
  • @IngoKarkat How to stop after multiline input? If I press Ctrl C it stops but the variable is not saved! – Porcupine Aug 13 '18 at 09:46
  • 1
    @Nikhil The Ctrl-C aborts your entire script; it's difficult to forward it to the subcommand. To skip reading input after N lines, I would use `head`: `myVar=$(head -n 10 /dev/stdin)` If that doesn't suit you, you probably need to implement a `read`-loop yourself. – Ingo Karkat Aug 13 '18 at 13:01
  • 1
    @Nikhil - use Ctrl-D instead of Ctrl-C to terminate input of a multi-line string here. – numbsafari May 24 '19 at 15:56
13

tee does the job

#!/bin/bash
myVar=$(tee)
Sergey Grigoriev
  • 709
  • 7
  • 15
  • +1 because tee is elegant and it works to read it in. Wow thanks! But you don't answer the question, how to print it with altering newlines. – CommaToast Sep 08 '14 at 22:21
13

[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%|||}"
DocSalvager
  • 2,156
  • 1
  • 21
  • 28
7

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.

leemes
  • 44,967
  • 21
  • 135
  • 183
7

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.

Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
  • 2
    Nice. The last two lines can be replaced with just `echo -n "${myVar%x}"` But fixing the variable first is more *modular* I guess. – antak Dec 01 '15 at 05:52
3

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..

  • I'm using `-r` for `read`. i.e.: `trim() { read -r str; str=${str##+([[:space:]])}; str=${str%%+([[:space:]])}; echo -n "${str}"; }`, and it works as : `$ echo "-$(echo ' a b ' | trim)-"` ⇢ `-a b-` – Marslo Jan 12 '23 at 08:38