12

I have a bash script, f, that contains python code. That python code reads from standard input. I want to be able to call my bash script as follows:

f input.txt > output.txt

In the example above, the python code will read from input.txt and will write to output.txt.

I'm not sure how to do this. I know that if I wanted to just write to a file, then my bash script would look like this

#!/bin/bash
python << EOPYTHON > output.txt
#python code goes here
EOPYTHON

I tried changing the second line in the code above to the following, but without luck

python << EOPYTHON $*

I'm not sure how else to go about doing this. Any suggestions?

EDIT I'll give a more concrete example. Consider the following bash script, f

#!/bin/bash
python << EOPYTHON 
import sys
import fileinput
for i in fileinput.input():
    sys.stdout.write(i + '\n')
EOPYTHON

I want to run my code with the following command

f input.txt > output.txt

How do I change my bash script so that it uses "input.txt" as the input stream?

user2699231
  • 155
  • 1
  • 2
  • 10
  • What's wrong with `f < input.txt > output.txt`? – rojomoke Mar 25 '14 at 09:24
  • 1
    "I have a bash script, f, that contains python code." ***Why***? You may find that code is easier to maintain if you stick to one language per source file. – johnsyweb Mar 25 '14 at 09:30
  • So I'm interpreting the problem as you want to know where to redirect python output to directly in the script (i.e. that the script was called redirecting stoudt to output.txt). My answer should solve that, but still a bit curious as to why you can't just print python to stdout and let bash handle the redirection from outside the script? – Reinstate Monica Please Mar 25 '14 at 09:33
  • @Johnsyweb You've never made a wrapper for a script written in another language? – Reinstate Monica Please Mar 25 '14 at 09:37
  • 1
    @BroSlow: Sure I have, but a wrapper can call out to an external file rather than include it. This separation of concerns promotes testability and maintainability. – johnsyweb Mar 25 '14 at 09:45

6 Answers6

30

As no one mentioned this, here is what author requested. The magic is to pass "-" as argument to cpython (instruction to read source code from stdin):

With output to file:

python - << EOF > out.txt
print("hello")
EOF

Execution sample:

# python - << EOF
> print("hello")
> EOF
hello

As data can't be passed via stdin anymore, here is another trick:

data=`cat input.txt`

python - <<EOF

data="""${data}"""
print(data)

EOF
Reishin
  • 1,854
  • 18
  • 21
  • Why is the `-` argument needed? Seems to me `python << EOF ... EOF` works just as well, unless you need to pass arguments to the `...`. – Big McLargeHuge Aug 18 '21 at 17:40
  • @BigMcLargeHuge check your `python --help` command. In some distros it is default argument, in some not. Omitting it is just defaulting to it on supported versions or binaries, while using it explicitly guaranties the result. – Reishin Aug 18 '21 at 18:51
  • this seems more on point than -c – Jason Newton Feb 09 '22 at 19:16
9

Updated Answer

If you absolutely must run the way you ask, you could do something like this:

#!/bin/bash
python -c 'import os
for i in range(3):
   for j in range(3):
     print(i + j)
'  < "$1"

Original Answer

Save your python code in a file called script.py and change your script f to this:

#!/bin/bash
python script.py < "$1"
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • I want a solution without saving another file in my directory – user2699231 Mar 25 '14 at 17:39
  • Please have another look - I have updated to use Bash's multi-line input when surrounded by single-quotes. – Mark Setchell Mar 25 '14 at 18:26
  • +1; using `-c` with a Python source-code string is indeed key: it enables passing stdin input to the Python code (as opposed to stdin input being interpreted as Python code, as happens with the `< – mklement0 Mar 25 '14 at 19:07
0

You can just check it against the file descriptor list for the process, i.e. on a proc filesystem you can print the redirection location for stdout with

readlink /proc/$$/fd/1

For example

> cat test.sh
#!/bin/bash
readlink /proc/$$/fd/1
> ./test.sh
/dev/pts/3
> ./test.sh > out.txt
> cat out.txt 
/home/out.txt
Reinstate Monica Please
  • 11,123
  • 3
  • 27
  • 48
0

-c option from @Mark Setchell's answer should work.

Here's an alternative where you embed bash in Python script:

#!/usr/bin/env python
import sys
import fileinput
from subprocess import call

# shell command before the python code
rc = call(r"""
some bash-specific commands here
...
""", shell=True, executable='/bin/bash')

for line in fileinput.input():
    sys.stdout.write(line) #NOTE: `line` already has a newline

# shell command after the python code
rc = call(r"""
some /bin/sh commands here
...
""", shell=True)
Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
0

Use process substitution for passing the python code. This frees up regular io indirection which can be used as usual.

❯ seq 1 5 > input.txt
❯ python3 <input.txt <(<< EOS
import sys
print("Running script with args: ", sys.argv)
for line in sys.stdin:
  print("line is %s" % line, end='')
EOS
)
Running script with args:  ['/proc/self/fd/11']
line is 1
line is 2
line is 3
line is 4
line is 5
balki
  • 26,394
  • 30
  • 105
  • 151
0

For the sake of completeness, you can also use bash process substitution to trick python into reading from the substituted file descriptor. An example I used recently:

IFUPDOWN_VERSION=$(apt-cache show ifupdown | grep -Po '(?<=^Version: ).*')

if ! python <(cat<<EOF
from distutils.version import LooseVersion, StrictVersion
if LooseVersion("${IFUPDOWN_VERSION}") < LooseVersion("0.8.36"):
  exit(1)
EOF
              ); then
    echo "ifupdown version doesn't support 'client no' option!"
    exit ${LINENO}
fi

This is similar to calling python myscript.py.

ealfonso
  • 6,622
  • 5
  • 39
  • 67