11

Is it possible to read user input from STDIN while tangling a source block with org-babel-tangle?

I am aware of this: Org Mode Babel - Interactive code block evaluation.

That doesn't help solve this particular use-case, as it still doesn't allow proper STDIN input from shell, but only simulates a limited input internally to Emacs.

Background

I would like to use Org's Babel to learn new programming languages (Perl and Bash) by executing some tutorials from one org file.

The problem is that many tutorials rely on STDIN. For example, if one runs the following perl tidbit:

#+BEGIN_SRC perl :tangle hello-name.pl  :results output :export code
use 5.010;
use strict;
use warnings;

say "What is your name?";
my $name=<STDIN>;
say "Hello $name, how are you?";

#+END_SRC

Emacs will not wait for the user's interaction to properly type a name on STDIN, and it will immediately output:

#+RESULTS:
: What is your name?
: Hello , how are you?

Same thing using a bash example. This:

#+BEGIN_SRC sh  :results output :export code :tangle dir-input.sh
#!/bin/bash

if [ -z "$TEST_DIR" ]
then
    echo "TEST_DIR was not set, please enter the path: "
    read input_variable
    export TEST_DIR=$input_variable
fi
#+END_SRC

Will not wait for user input, and Emacs will immediately return this:

#+RESULTS:
: TEST_DIR was not set, please enter the path: 

Is there a native way for Emacs to wait for input on an executing tangled block?

If not, would you please give some pointers on how to write something like a tangle-and-run-via-shell-buffer function that would:

  • Tangle the code block at point, saving with given filename,
  • execute the corresponding file in a visible shell buffer,
  • possibly accepting input from the user,
  • and finally reporting STDOUT, if any, to #+RESULTS:?

If such feature is not implemented (yet) in Org, how could one implement it with elisp?


Update: After searching and studying more the Emacs and elisp manuals, it seems the way to do it would be leveraging Comint, like perhaps make-comint-in-buffer.

(make-comint-in-buffer "*cmd-buffer*" nil "perl" nil "hello-name.pl")

Unfortunately, that is over my head right now 😣

1 Answer 1

6

Try this

Note: Made following minor changes to your code block:

  • Moved #!/bin/bash into code block header :shebang #!/bin/bash to automatically set executable file permissions when block is tangled into dir-input.sh.

  • Added debug code to show $TEST_DIR was assigned correctly from read input_variable.

#+BEGIN_SRC sh  :results output :export code :tangle dir-input.sh :shebang #!/bin/bash

if [ -z "$TEST_DIR" ]
then
    echo "TEST_DIR was not set, please enter the path: "
    read input_variable
    export TEST_DIR=$input_variable
    echo "# export TEST_DIR=$TEST_DIR"
fi
#+END_SRC   

Then created new code block to call tangled file ./dir-input.sh.

#+BEGIN_SRC sh :results output :shebang #!/bin/bash  :var USER_INPUT=(read-string "Test Dir: ")
  echo $USER_INPUT | ./dir-input.sh 
#+END_SRC

Notice header :var USER_INPUT=(read-string "Test Dir: ")

This header will display a Test Dir: prompt in the minibuffer window when the code block is executed using a C-c C-c.

Enter the path, e.g. /path/to/test/dir enter

and the block will pass the input to the ./dir-input.sh via STDIN. You should see the following #+RESULTS:

#+RESULTS:
: TEST_DIR was not set, please enter the path: 
: # export TEST_DIR=/path/to/test/dir

Hope that helped!


Code tested with:
GNU Emacs 24.4.1 (x86_64-apple-darwin14.0.0, NS apple-appkit-1343.14) of 2014-12-25
org-mode version: 8.3.2

6
  • That helps quite a bit, thank you. A creative way of using vars, very instructive. I am wondering how would I go to capture STDIN "wholly", sort of speak, i.e. like one could from native shell? For example, to be able to read new lines and control chars (beside CTRL-D)?
    – gsl
    Commented Feb 4, 2016 at 9:12
  • 1
    @gsl - BTW - I'm still working on a new answer to question about multiple lines and control characters. If you figure it out before I do please post your answer.
    – Melioratus
    Commented Jun 23, 2018 at 21:18
  • Thank you so much for preparing a new answer. I still am looking at a working solution. I cannot figure it out by myself, still over my head right now. I am choosing your answer, and when your new one will come up, select it later.
    – gsl
    Commented Jun 25, 2018 at 8:21
  • Hi @melioratus - were you able to find a way to deal with multiple lines and control characters? That would be quite useful in many cases.
    – gsl
    Commented Mar 12, 2020 at 8:33
  • 1
    @gsl - Thanks for following up! You’re correct reading from stdin would be really useful and I’m still looking! I’ve made progress reading stdin into named buffer from command line and then calling elisp to read multiline buffer into variable. This works when reading from a pipe but unfortunately not for streaming stdin yet, i.e. tail file | works but not tail -f file |. I’ll look through my notes and add my partially working multiline example as new answer. Thanks for reminder!
    – Melioratus
    Commented Mar 12, 2020 at 12:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.