I am looking for a program or (probably C)-code which I will refer to as route
which takes a bytestring/stream on stdin and routes it to one of n programs, based on the intial few bytes (the prefix). The prefix will not be forwarded/piped to the destination program. I would imagine it would have a commandline interface like:
echo -n 'for-p1.perform-operation-A' | route 'for-p1.' >(program1) 'for-p2.' >(program2)
echo -n 'for-p2.perform-operation-B' | route 'for-p1.' >(program1) 'for-p2.' >(program2)
in the example given above, program1 would receive 'perform-operation-A' (as if I had executed `echo 'perform-operation-A' | program1), program2 would receive 'perform-operation-B'. The prefix always precedes the (virtual) filename of the destination.
Is there any existing solution for doing this or do I have to roll my own?
EDIT: By popular request, here is my 30min attempt at a solution, but I would vastly prefer existing solutions, or at least recommendations for libraries for the steps:
/*
build:
sudo apt install -y build-essential && g++ main.cpp
usage example:
(
rm output* || true
echo -n "foo-hello" | ./a.out 2>/dev/null 'foo-' >(cat | tee -a output-foo) 'bar-' >(cat | tee -a output-bar) 1>/dev/null
echo -n "bar-world" | ./a.out 2>/dev/null 'foo-' >(cat | tee -a output-foo) 'bar-' >(cat | tee -a output-bar) 1>/dev/null
echo
echo -n "output-foo: " ; cat output-foo ; echo
echo -n "output-bar: " ; cat output-bar ; echo
)
*/
#include <fcntl.h>
#include <stdio.h>
#include <map>
#include <string>
using namespace std;
typedef FILE* File;
#define hasKey(map, key) (map.find((key)) != map.end())
// I assume this is pretty inefficient, looking for a good solution
void pipeRest(File fromFile, File toFile) {
size_t bytesRead = 0;
do {
char data;
bytesRead = fread(&data, 1, 1, fromFile);
if (bytesRead) fwrite(&data, 1, 1, toFile);
} while (bytesRead > 0);
}
int main(
int const argc,
char const * const * const argv
) {
if (argc <= 1) {
fprintf(stderr, "usage:\n\n\troute prefix1 >(ouput-program-1) prefix2 >(output-program-2) ...\n\nreads from stdin, recognizes any of n prefixes and pipes the rest to the filename following the prefix\n");
exit(1);
}
// Parse [prefix outputFile] pairs from commandline args
map<string, string> prefixesToOutputFileNames;
map<string, File> prefixesToOutputFiles;
for (int i = 0; i < argc; i++) {
fprintf(stderr, "argv[%d] = '%s'\n", i, argv[i]);
if (i > 0 && i % 2 == 0) {
auto const prefix = argv[i - 1];
fprintf(stderr, "prefix = '%s'\n", prefix);
auto const outputFileName = argv[i];
fprintf(stderr, "outputFileName = '%s'\n", outputFileName);
prefixesToOutputFileNames[prefix] = outputFileName;
auto const outputFile = fopen(outputFileName, "wb");
prefixesToOutputFiles[prefix] = outputFile;
}
}
// Start reading bytes from stdin, collect the prefix
freopen(0, "rb", stdin);
string prefix = "";
char nextPrefixChar[2] = { 0 };
size_t bytesRead = 0;
do {
bytesRead = fread(nextPrefixChar, 1, 1, stdin);
prefix += nextPrefixChar;
fprintf(stderr, "read %zd bytes = '%s', prefix = '%s'\n", bytesRead, bytesRead ? nextPrefixChar : 0, prefix.c_str());
if (hasKey(prefixesToOutputFiles, prefix)) {
// Prefix found -> pipe to corresponding output file
auto const outputFileName = prefixesToOutputFileNames[prefix];
fprintf(stderr, "prefix '%s' was recognized, will pipe rest to '%s'\n", prefix.c_str(), outputFileName.c_str());
auto const outputFile = prefixesToOutputFiles[prefix];
pipeRest(stdin, outputFile);
exit(0);
}
} while (bytesRead > 0);
fprintf(stderr, "input ends and did not recognize any prefix, rest will be piped to stdout\n");
pipeRest(stdin, stdout);
return 0;
}