0
#!/bin/bash
tty=$(readlink /proc/$$/fd/2)

echo "Paste Content below"
readarray -d '\n' lines < $tty
printf '%s\n' "${lines[@]}" > links.txt

So i am using the above script to store a block of text in a variable lines and write it to a file links.txt . The thing is when the readarray prompt shows up and i paste my text , i have to stop the read process by doing ctrl + d , after that the script proceeds further and writes the stored variable to links.txt

Ideally i want some particular keyword to act as endpoint of my paste where the read prompt ends automatically as soon as that particular word is detected For example , after i have pasted my content. if i enter stopread on a new line it should stop the read process and proceed further with the script ( i have just used the word stopread as an example )

Example - In readarray prompt , if i paste

1
2
3
stopread

it should store 1 ..3 in variable lines and and proceed further in script without the need of doing ctrl + d to stop close read prompt

Sachin
  • 1,217
  • 2
  • 11
  • 31
  • Are there any other commands after `readarray` that read from the stdin (e.g. prompt the user for input)? – oguz ismail Aug 08 '20 at 18:49
  • 2
    Well then there is a problem here. You can't discard the lines following `stopread` reliably, the command that *stores single line output* would read the line that immediately follows `stopread`. Try `readarray lines < <(sed '/stopread/q' /dev/tty)` and see how it fails for yourself. – oguz ismail Aug 08 '20 at 18:59
  • @oguzismail Yes , there is one more read command below this ( not readarray) , it stores single line output so just pressing enter does the job for read.. also i am writing both the readarray and read to tty , which probably you remember , i didnt added it here as it wasnt the main focus so i kept code as simple as possible – Sachin Aug 08 '20 at 19:03
  • 1
    You need a loop, not `readarray`/`mapfile`. – Shawn Aug 08 '20 at 19:15
  • 1
    And file descriptor 2 is standard *error*. 0 is *input* and what you should be reading from. – Shawn Aug 08 '20 at 19:16
  • @Shawn thanks for suggestion , i dont really have much info on file descriptor , i was using it as told by this answer to one of my questions - https://stackoverflow.com/questions/63313302/how-do-i-fix-bash-error-dev-tty-no-such-device-or-address – Sachin Aug 08 '20 at 19:21
  • `readarray -d '\n' lines` reads ```\```-separated strings into lines, not newline-separated. You'd need `readarray -d $'\n' lines` to read 1 line at a time into `lines` but that's not useful for what you're trying to do anyway. – Ed Morton Aug 09 '20 at 12:15
  • @Sachin why do you keep trying to use an array to store your input instead of just a scalar variable? There's no indication in your past 2 questions that you need an array and a scalar is much easier to manage. – Ed Morton Aug 09 '20 at 12:25

2 Answers2

2

You should be doing this with a while read loop, since this will allow you to only read the necessary data and no more (due to a buffer size of 1), allowing the rest of the script to consume the rest of the input in a robust way:

lines=()
while IFS= read -r line && [[ "$line" != "stopread" ]]
do
  lines+=("$line")
done

However, since you ask how to do it readarray, you can do:

readarray -d '\n' lines < <(sed '/stopread/q')

This inherently reads in larger buffer sizes, so it may overconsume input. For example, if the stop word appears half way into a buffer, then the rest of the buffer is necessarily discarded. It's even worse than just being fragile and unpredictable, because you won't be able to spot any problems in casual testing at typical human input speeds, and may therefore more easily be fooled into thinking it's a good solution.

that other guy
  • 116,971
  • 11
  • 170
  • 194
  • Thanks a lot for the explanation and recommendation for while loop , i tested both the solutions and while loop solution works perfectly .Personally my purpose is served and i am gonna use the while loop solution , although for viewers of this post , maybe you can debug the readarray solution , currently there are 2 small issues with it 1) it does work if i use tty - readarray -d '\n' lines < <(sed '/stopread/q') < $tty 2) Without tty ,it works but it even stores the word 'stopread' in the variable along with the lines prior to it – Sachin Aug 08 '20 at 19:41
  • `readarray -d '\n' lines < <(sed '/stopread/q')` populates `lines[0]` with all of the input lines up to and including `stopread` and the newline after it. ITYM to use `$'\n'` rather than `\n'` as the delimiter but that still includes `stopread` in `lines[]`. I can't imagine why adding `< $tty` would make any difference but none of this is necessary anyway. – Ed Morton Aug 09 '20 at 12:13
1

Just like in your previous question, an array isn't useful for what you're trying to do:

$ foo=$(awk '/stopread/{exit} 1' file)
$ echo "$foo"
1
2
3

or to read from stdin:

$ foo=$(awk '/stopread/{exit} 1')
1
2
3
stopread
$ echo "$foo"
1
2
3
Ed Morton
  • 188,023
  • 17
  • 78
  • 185