28

Is there any way to create a virtual file, such that reading from the file actually reads from the stdout of a command; writing to the file is acually writing to the stdin of a command?

So far I have kludged this with an inotifywait on a file, which calls a command when the file is modified, taking it's input from the file and writing back to it.

I don't like that the inotifywait has to be constantly restarted though (and I have to ensure that it is always running). I only use this file perhaps twice a week.

jsj
  • 1,410

5 Answers5

22

You may be looking for a named pipe.

mkfifo f
{
  echo 'V cebqhpr bhgchg.'
  sleep 2
  echo 'Urer vf zber bhgchg.'
} >f
rot13 < f

Writing to the pipe doesn't start the listening program. If you want to process input in a loop, you need to keep a listening program running.

while true; do rot13 <f >decoded-output-$(date +%s.%N); done

Note that all data written to the pipe is merged, even if there are multiple processes writing. If multiple processes are reading, only one gets the data. So a pipe may not be suitable for concurrent situations.

A named socket can handle concurrent connections, but this is beyond the capabilities for basic shell scripts.

At the most complex end of the scale are custom filesystems, which lets you design and mount a filesystem where each open, write, etc., triggers a function in a program. The minimum investment is tens of lines of nontrivial coding, for example in Python. If you only want to execute commands when reading files, you can use scriptfs or fuseflt.

  • In your example, is there any way to "reset" the named pipe, so when it's read again, it re-executes those three commands? Or do you have to delete and re-create the pipe? – Cerin Nov 04 '22 at 19:35
  • @Cerin You don't need to delete the pipe. You have to restart the reader commands and writer commands, they aren't launched automatically. – Gilles 'SO- stop being evil' Nov 04 '22 at 20:05
  • @gilles-so-stop-being-evil How do you restart the writer command? They way you've written it, it's just a file handle with no named script. How do you "restart" your echo-sleep-echo script? – Cerin Nov 06 '22 at 01:26
  • @Cerin A named pipe is just a “handle” for programs to open and communicate on. It doesn't have any code associated to it. – Gilles 'SO- stop being evil' Nov 06 '22 at 11:44
  • @gilles-so-stop-being-evil Exactly. That's my point. You can't restart the writer in your example, because it's an inline script. Therefore, the only solution to effectively restart it is to delete the pipe and re-create it with another inline script as the writer. – Cerin Nov 07 '22 at 14:39
  • @Cerin You don't need to call rm and mkfifo again. If the named pipe already exists, you can reuse it. – Gilles 'SO- stop being evil' Nov 07 '22 at 15:02
5

Some things to be aware of regarding named pipes: A pipe cannot - unlike a file - have multiple readers. Once its content is read it's gone. So you need to loop, i.e. you'll need to constantly push content into the pipe from the writer.

I've been searching for an answer myself to this question and the only other thing I've been able to come up with is scriptfs which is based on fuse.

1

The easiest way to "Create a virtual file that is actually a command" is to extend the FUSE filesystem. It is very easy to do with libraries like fusepy.

Here is an example of an FUSE filesystem that lets you read HTTP/HTTPS/FTP files as if they were present on your own Linux box. You can similarly create something that runs any command for the same on read.

If you look at the source code it is hardly 50 lines of code (excluding any boilerplate code).

supersan
  • 111
1

What you describe is essentially a FIFO special file, see fifo(7). They are created by mkfifo(1), and a process can then open them under Linux for R/W (not all Unixy systems handle that). Two independent processes can then use it to communicate.

vonbrand
  • 18,253
-1

The question is ambiguous. stdin/stdout are pipes/streams. A file is random access. In other words, when the file is accessed, offsets will be involved, and there is no way to pass these on to the command through stdin/stdout.

If you truly want pipe/stream behavior without addresses, then use named pipes, or equivalently setup virtual terminals (pty), for example using "socat". your command then listens and responds on the other end.But then whatever is accessing the file would have to do so as a serial device. I'm not sure how any file path in linux can simply be accessed "as a stream/pipe" just like that. The "cat" command for example would actually programmatically open the file, but not sure of the specifics, but I don't think it would open it the same way it would open any regular filesystem file, as that would then involve addresses.

If you need random access, then research NBD, NBDkit, libFUSE, BUSE, scriptfs, python-fuse.

mo FEAR
  • 192