28

Say I have a command foo which takes a filename argument: foo myfile.txt. Annoyingly, foo doesn't read from standard input. Instead of an actual file, I'd like to pass it the result of another command (in reality, pv, which will cat the file and output a progress meter as a side effect).

Is there a way to make this happen? Nothing in my bag of tricks seems to do it.

(foo in this case is a PHP script which I believe processes the file sequentially).

I'm using Ubuntu and Bash

EDIT Sorry for the slightly unclear problem description, but here's the answer that does what I want:

pv longfile.txt | foo /dev/stdin

Very obvious now that I see it.

Steve Bennett
  • 5,539
  • 12
  • 45
  • 57

5 Answers5

27

If I understand what you want to do properly, you can do it with bash's command substitution feature:

foo <(somecommand | pv)

This does something similar to what the mkfifo-based answers suggest, except that bash handles the details for you (and it winds up passing something like /dev/fd/63 to the command, rather than a regular named pipe). You might also be able to do it even more directly like this:

somecommand | pv | foo /dev/stdin
Gordon Davisson
  • 11,036
  • 3
  • 27
  • 33
  • 3
    oh, nice one! This `pv inputfile.txt | foo /dev/stdin` is exactly what I was looking for, but didn't think of. – Steve Bennett Jun 18 '12 at 05:13
  • 1
    Woot! /dev/stdin – sage Jan 19 '15 at 22:43
  • `<(.)` worked for me. Is this syntax documented in the Bash manual? I understand `(.)` as command substitution is documented, but did not see its combination with `<` explained. – flow2k Feb 02 '19 at 03:12
  • @flow2k It's called "process substitution" -- see [section 3.5.6 of the bash manual](https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html). – Gordon Davisson Feb 02 '19 at 04:03
  • Got it. You're right - it mentions that it uses named pipes/`mkfifo`. – flow2k Feb 02 '19 at 06:24
  • 2
    If you're using this in a script, note you'll need to start the script with `#!/bin/bash` rather than `#!/bin/sh` otherwise this feature will throw an error instead. – Malvineous Feb 29 '20 at 11:28
3
ARRAY=(`pv whatever`); for FILE in $ARRAY; do foo $FILE; done
Hyppy
  • 15,458
  • 1
  • 37
  • 59
2

This Unix SE question has a an answer that shows how to create a temporary named pipe:

Shell Script mktemp, what's the best method to create temporary named pipe?

Based on the answers there, you could do something like the following:

tmppipe=$(mktemp -u)
mkfifo -m 600 "$tmppipe"
pv examplefile > $tmppipe

You could then watch the pipe with:

foo $tmppipe

Then delete it when you're done with it.

Dave Forgac
  • 3,486
  • 7
  • 36
  • 48
1

Try using mkfifo.

mkfifo file_read_by_foo; pv ... > file_read_by_foo 

In another shell, run foo file_read_by_foo

Steve Bennett
  • 5,539
  • 12
  • 45
  • 57
becomingwisest
  • 3,278
  • 19
  • 17
0

xargs seems to work well for this:

echo longfile.txt | xargs pv
MadHatter
  • 78,442
  • 20
  • 178
  • 229
  • That does something different: running `pv` multiple times, each with one or more lines drawn from `longfile.txt`. Not at all what I'm after here. – Steve Bennett Jun 18 '12 at 05:17