4

I am writing a git hook that might require human input. According to this answer one has to use exec < /dev/tty in that script. This does the job, but now there is no possibility to redirect the standard output to that hook (for test purposes). I guess the problem can be narrowed down to a question: how to send a message to /dev/tty in such a way that another process will read it? Not sure if this is even possible.

Here is the minimum reproducible example:

# file: target.sh

exec < /dev/tty # we want to use /dev/tty
read -p  "Type a message: " message
echo "The message ${message}"

I tried several solutions like this:

echo -e "foo\n"| tee /dev/tty | source target.sh

And it actually prints the message in the console after the read prompt, but the message variable remains unset. Is there any way to fix it?

Potherca
  • 13,207
  • 5
  • 76
  • 94

2 Answers2

2

You could make the input file an optional parameter:

#!/bin/bash

input_file=${1:-/dev/tty}
read -p  "Type a message: " message < "${input_file}"
echo "The message ${message}"

# other stuff ...

Now test the command like this:

your_script
your_script <(echo foo)
some_cmd | your_script
some_cmd | your_script <(echo foo)

PS: The syntax <(echo foo) which I'm using is a so called process substitution.

hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • Thanks for the prompt reply, I upvoted your approach as I like it, but I can't mark it as accepted. I am planning to use it in the git hook, there will be no option to pass the input file parameter. And I am curious if there any way to solve this `/dev/tty` issue. Anyways, your solution looks like a good practice for writing the custom scripts, thanks – Common Sense May 30 '20 at 07:27
  • The input file parameter defaults to /dev/tty, that's the point. In the git-hook you just don't pass a filename (check the examples). Use expect if you prefer that, but that's imo way more complicated, plus it requires that users have expect installed – hek2mgl May 30 '20 at 14:52
  • Sorry, It's my fault, I wasn't clear enough. I was assuming end-to-end tests. For example something like `echo -e "some information\n" | git commit -m "message"` (it's not a working example just to show my point). In this case `git` shadows the parameter and I am not sure if it's possible to override. Yes, users might not have `expect` installed, but they are not supposed to. It's enough to have it in CI environment. But you actually gave me another hint :D, I can test the hook separately as a regular script, without any `git` calls. – Common Sense May 30 '20 at 15:51
2

You can use expect to achieve the result:

#!/bin/bash

expect << EOF
spawn bash target.sh
expect {
    "Type a message: " {send "foo\r"; interact}
}
EOF
Philippe
  • 20,025
  • 2
  • 23
  • 32
  • Wow, thanks, that's the solution I was looking for. And it actually plays well with the test cases I was about to write – Common Sense May 30 '20 at 13:25