I tried to use the read/write file descriptor in bash so that I could delete the file that the file descriptor referred to afterward, as such:
F=$(mktemp)
exec 3<> "$F"
rm -f "$F"
echo "Hello world" >&3
cat <&3
but the cat
command gives no output. I can achieve what I want if I use separate file descriptors for reading and writing:
F=$(mktemp)
exec 3> "$F"
exec 4< "$F"
rm -f "$F"
echo "Hello world" >&3
cat <&4
which prints Hello world
.
I suspected that bash doesn't automatically seek to the start of the file descriptor when you switch from writing to reading it, and the following combination of bash and python code confirms this:
fdrw.sh
exec 3<> tmp
rm tmp
echo "Hello world" >&3
exec python fdrw.py
fdrw.py
import os
f = os.fdopen(3)
print f.tell()
print f.read()
which gives:
$ bash fdrw.sh
12
$ # This is the prompt reappearing
Is there a way to achieve what I want just using bash?
When bash starts it opens the three standard file descriptors: stdin (file descriptor 0), stdout (file descriptor 1), and stderr (file descriptor 2). You can open more file descriptors (such as 3, 4, 5, ...), and you can close them. You can also copy file descriptors.
Stdin, stdout, and stderr On a Unix-like operating system, the first three file descriptors, by default, are STDIN (standard input), STDOUT (standard output), and STDERR (standard error).
From man bash : -s If the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell.
You can use /proc file system or the lsof command to find all the file descriptors used by a process.
I found a way to do it in bash, but it's relying on an obscure feature of exec < /dev/stdin
which actually can rewind the file descriptor of stdin according to http://linux-ip.net/misc/madlug/shell-tips/tip-1.txt:
F=$(mktemp)
exec 3<> "$F"
rm -f "$F"
echo "Hello world" >&3
{ exec < /dev/stdin; cat; } <&3
The write descriptor isn't affected by that so you can still append output to descriptor 3 before the cat.
Sadly I only got this working under Linux not under MacOS (BSD), even with the newest bash version. So it doesn't seem very portable.
If you ever do happen to want to seek on bash file descriptors, you can use a subprocess, since it inherits the file descriptors of the parent process. Here is an example C program to do this.
seekfd.c
#define _FILE_OFFSET_BITS 64
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
/* Arguments: fd [offset [whence]]
* where
* fd: file descriptor to seek
* offset: number of bytes from position specified in whence
* whence: one of
* SEEK_SET (==0): from start of file
* SEEK_CUR (==1): from current position
* SEEK_END (==2): from end of file
*/
int fd;
long long scan_offset = 0;
off_t offset = 0;
int whence = SEEK_SET;
int errsv; int rv;
if (argc == 1) {
fprintf(stderr, "usage: seekfd fd [offset [whence]]\n");
exit(1);
}
if (argc >= 2) {
if (sscanf(argv[1], "%d", &fd) == EOF) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(1);
}
}
if (argc >= 3) {
rv = sscanf(argv[2], "%lld", &scan_offset);
if (rv == EOF) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(1);
}
offset = (off_t) scan_offset;
}
if (argc >= 4) {
if (sscanf(argv[3], "%d", &whence) == EOF) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(1);
}
}
if (lseek(fd, offset, whence) == (off_t) -1) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(2);
}
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With