1

I have a python script that prints to the stdout a list of files, for example:

$ ./myscript.py "file a.txt" "file b.txt" "file c.txt"

I want to open these files in LESS

$ less <(./myscript.py)

But I get the following error:

-bash: /dev/fd/63: Permission denied $ Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> BrokenPipeError: [Errno 32] Broken pipe

I think it may be the same problem as @Jon-Clements refers to in grep: broken pipe error python 2.2, i.e. the pipe is finished; but I'm not sure. I also tried (maybe incorrectly) the solution by @chepner store return value of a Python script in a bash script but it gives the same error.

How can I fix this? I know that mixing BASH and Python is bad practice.

BTW, the python script is using a loop around the following statement to produce the list of filenames:

print('"'+filename+'"',sep=" ",end=' ',) Also, when I copy and paste the output manually to less, it works fine.

Tim
  • 291
  • 2
  • 17
  • `the python script is using a loop around the following statement to produce the list of filenames` - Bash can very well do this, and you don't need Python – Inian Mar 29 '18 at 08:05
  • the python script is rather more complicated than just that one line: it filters a bigger set of files by content. I included this snippet because I thought it might need editing – Tim Mar 29 '18 at 08:24
  • Can you change the separator between them to null, and remove the quotes? If so you can pipe into xargs. – 123 Mar 29 '18 at 08:27

1 Answers1

1

Adding quotes around the file name isn't helpful. What you should do is

  1. Output each filename will a null byte between them.
  2. Pipe the output to xargs -0.

Quoting the filenames won't help unless you somehow use eval, which is not a good idea for security reasons. Your command should look something like

./myscript.py | xargs -0 less

As a hack, if the filenames are simple (i.e. no whitespace, no glob characters), you could modify ./myscript.py to output just the space-separated file names (no quotes), then run

less $(myscript.py)

since less only takes filename arguments.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Your solution works. I have whitespaces in filenames, hence my quoting. Can you explain 2 things: Firstly, why does typing `less "foo bar.txt"` at the prompt work fine, but a python script returning `"foo bar.txt"` cannot be used in the <($...) way? Secondly, I thought that quoting was good security, e.g. https://unix.stackexchange.com/questions/171346/security-implications-of-forgetting-to-quote-a-variable-in-bash-posix-shells – Tim Mar 29 '18 at 13:44
  • 1
    Quoting is good security, which is why `less $(myscript.py)` is only safe if you know that the files returned by `myscript.py` won't be changed by word-splitting or pathname expansion. When `less "foo.txt"` is processed by the shell, it removes the syntactic quotes before passing the string `foo.txt` to `less`. When the quotes are part of a parameter expansion, though, such as `f='"foo.txt"'; less $foo` (or `less "$foo"` in this case), the quotes are literal characters in the value of the parameter, and *not* subject to quote removal. – chepner Mar 29 '18 at 13:49