Friday, August 19, 2011

A Smart but Neglected BASH Feature

I had a humongous shell script and a wish to redirect stderr for a boat load of commands in one fell swoop. I could have used the {} grouping but for obscure reasons it was not appropriate.

The answer was to write a Bash extension in C which takes advantage of two things:
a) Bash does not fork(2) when it executes an extension so it's in-process;
b) dup2(2)
so it's possible to do funky things with the file descriptors and get away with it.

Alas on Win32 Cygwin's bash does not load this extension (MinGW does) and dup2(2) is just borked. Blame M$ for designing a braindead C library and OS.

Here's the code:
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <asm/fcntl.h>
#include <errno.h>

#include "builtins.h"
#include "shell.h"

// Compile on Linux/x86:
// gcc -I/tmp/bash-3.2 -I/tmp/bash-3.2/include fdmangle.c -fpic -c
// ld -x -Bshareable -o fdmangle.o
// Use in shell scripts:
// enable -f ./ fdmangle
// fdmangle 2 stderr

extern char **make_builtin_argv(); // Bash-ism

static int verbose = 0;

static int fdmangle_main(int argc, char **argv)
if(argc < 3) {
return 1;

int n = 1;
if(!strcmp(argv[1], "-v")) { verbose = 1; n++; }

if(verbose && argc < 4) {
return 1;

const int fdn = atoi(argv[n]);
const char* file = argv[n+1];

if(verbose) fprintf(stderr, "fdmangle %d -> %s\n", fdn, file);

int flags = O_CREAT | O_WRONLY | O_APPEND;
#ifdef __unix__
flags |= O_NOFOLLOW;
int fd = open(file, flags, 0640);
if(fd < 0) {
fprintf(stderr, "Cannot open for writing %s: %d (%s)\n", file, errno, sys_errlist[errno]);

dup2(fd, fdn);

return 0;

static int fdmangle_builtin(WORD_LIST *list)
char **v=NULL;
int c=0, r=0;

v = make_builtin_argv(list, &c);
r = fdmangle_main(c, v);

return r;

static char* fdmangle_doc[] = {
"File descriptor mangling",
(char *)0

struct builtin fdmangle_struct = {
"fdmangle [-v] fd file",