43

I've been googling this question to no avail. I'm automating a build process here at work, and all I'm trying to do is get version numbers and a tiny description of the build which may be multi-line. The system this runs on is OSX 10.6.8.

I've seen everything from using CAT to processing each line as necessary. I can't figure out what I should use and why.

Attempts

read -d '' versionNotes 

Results in garbled input if the user has to use the backspace key. Also there's no good way to terminate the input as ^D doesn't terminate and ^C just exits the process.

read -d 'END' versionNotes 

Works... but still garbles the input if the backspace key is needed.

while read versionNotes do echo " $versionNotes" >> "source/application.yml" done 

Doesn't properly end the input (because I'm too late to look up matching against an empty string).

3
  • You're getting this information from the user, correct? Commented Apr 9, 2012 at 23:14
  • Correct; I want the user to enter this information in the terminal when executing the script. Commented Apr 10, 2012 at 11:22
  • 2
    You haven't made yourself clear, I'd rather say. Commented Jun 11, 2012 at 10:33

11 Answers 11

38

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 $ echo "$myVar" hello this is test 

and I agree this is worth mentioning — echo "$myVar" would have displayed the input as it was given.

4
  • 1
    How to stop after multiline input? If I press Ctrl C it stops but the variable is not saved! Commented Aug 13, 2018 at 9:46
  • 2
    UNIX® terminals do have "discipline": en.wikipedia.org/wiki/Line_discipline • Check it out what does "eof" mean • Do read man stty, find out how to get current settings Commented Aug 13, 2018 at 15:43
  • 3
    ctrl-d to stop. Commented Oct 23, 2018 at 2:52
  • A complement from this answer, if you want to write directly into a file, simply use echo "$(</dev/stdin)" > /tmp/file. Commented Mar 21, 2023 at 10:19
25

Try this:

user@host:~$ read -d '' x <<EOF > mic > check > one > two > EOF

Without line breaks:

user@host:~$ echo $x mic check one two

With line breaks:

user@host:~$ echo "$x" mic check one two
1
  • @Bruno My pleasure; it's a tricky one. Especially at first glance, it looks fairly standard. Can be quite useful though, once you get the hang of the various idiosyncrasies. Commented Mar 11, 2017 at 8:57
10

Refer to the excellent Bash Guide for all your bash scripting needs.

In particular the Bash FAQ contains this at number #1:

How can I read a file (data stream, variable) line-by-line (and/or field-by-field)?

2
  • You say to use read -r, but how do I get it to do so from STDIN? I did attempt to use read -r early on. Commented Apr 25, 2012 at 17:17
  • 1
    It reads from stdin by default. Commented Apr 26, 2012 at 7:17
3

You can start an editor like vim, pico...

${VISUAL:-${EDITOR:-vi}} source/application.yml 
4
  • Um... how does that answer the OP's question in any way ? Commented Apr 25, 2012 at 13:27
  • He can start the editor from the script. Commented Apr 25, 2012 at 14:46
  • 1
    And that accomplishes what, exactly ? He wants to use text from a file in the script, not write information to a file. Commented Apr 25, 2012 at 15:05
  • He wasnts to read text from the user and write it to a file. Please read the comments added to the question. Commented Apr 25, 2012 at 16:03
2

I've solved this issue by dealing with each line until I came up with a blank line. It works well enough for my situation. But if someone wants to add a better solution, feel free to do so.

echo "--- notes: |" > 'version.yml' while read line do # break if the line is empty [ -z "$line" ] && break echo " $line" >> "source/application.yml" done 
1
  • 1
    Use read -r instead. Commented Apr 25, 2012 at 13:26
2
~ $ txt=; while read line; do [[ $line == '.' ]] && break; txt+="$line\n"; done; txt=${txt:0:-2} Hi! This should work, right? Cheers! . ~ $ echo $txt Hi! This should work, right? Cheers! ~ $ 

This should work in zsh! 😃

txt=${txt:0:-2} – To remove the last newline.

P.S. I think this function will read multilines into the variable named as the $1 given to this function.

# e.g. 'readlines blaat' will set $blaat to the multilines input. readlines() { eval "unset $1" while read line do [[ $line == '.' ]] && break eval "$1+='$line\n'" done eval "$1=\${$1:0:-2}" } 
0
1

First a few corrections:

  1. To allow "edition" on the line use -e which uses readline (so you have the bash history and all editing features)
  2. -d only takes one character. E.g. from 'END' takes 'E' and whenever the user writes an 'E' the reading stops (I guess that's not what you want...)

There are a few possibilities to do this. I'd go for read line by line and stop when an empty line is found (though you could set any stop word):

unset tmp while : do read line [[ $line == "" ]] && tmp="${tmp:0:$((${#tmp}-1))}" && break tmp="$tmp"$line$'\n' done 
1

We are using a construct using xargs and ctrl-d for breaking. I'm not perfectly satisfied with it, but it certainly does the job of taking multi-line user input and stuffing that into a variable (formatting intact). (The first and third assignments add quotes around the contents of the xargs input.)

 printf '%s\n' "When finished hit ctrl-d on a new line to proceed. " "" "" # this will load the user's input into a variable instead of a file reminderBody="\"" reminderBody+=$( xargs -0 ) reminderBody+="\"" 

We use reminderBody as the body of an e-mail being sent by mail (via bash).

1

As in your comment you are stating that the input comes from a user via terminal: You can use read to read multi line input via

read -d $'\04' versionNotes 

which sets the delimiter to EOF, which is 0x04 in Unicode so you can read input until CTRL-D is given.

0

You have several methods.

One of the simplest methods is:

MYVAR=$(yourcommand) echo $"MYVAR" 

For example:

MYVAR=$(ls /) echo $"MYVAR" 
4
  • Except I'm not executing a shell command like LS. I'm requesting multi-line input from the user. Commented Apr 10, 2012 at 11:23
  • This is very bad practice; do not process the output of ls! Commented Apr 25, 2012 at 13:14
  • @adaptr We've heard it all before, would you mind suggesting a good alternative, please? Commented Mar 11, 2017 at 8:50
  • @voices, prefer find over ls, for example. Commented Dec 18, 2024 at 20:38
-1

See: http://tldp.org/LDP/abs/html/internal.html#READR

Use read and note that if you end a line with \ the newline is ignored. So you could do:

 #!/bin/bash read -p "version: " version echo $version # enter some input and end long lines with "\", then plain enter at the end read -p "description: " description echo -e "$version\n$description >> yourfile.txt 
4
  • If I just wanted a paragraph with line breaks ignored, yes. But as this is a generated list of release notes for a build script, I need to be able to preserve those line breaks; I might try to enter a bulleted list, for example. Commented Apr 25, 2012 at 13:09
  • The ABS is a monstrosity of horrific proportions. 90% of it is plain wrong. Commented Apr 25, 2012 at 13:26
  • It's not that bad. Commented Mar 11, 2017 at 8:45
  • Also, running read without -r (which disables escapes on inputs) while taking user input is a bad security practice. Commented Dec 18, 2024 at 20:39

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.