Skip to content

Instantly share code, notes, and snippets.

@konsolebox
Created June 19, 2025 04:20
Show Gist options
  • Save konsolebox/203ec311af1d37da4aec37e36f47248b to your computer and use it in GitHub Desktop.
Save konsolebox/203ec311af1d37da4aec37e36f47248b to your computer and use it in GitHub Desktop.
/* Runs command while optionally redirecting stdout,
* stderr and stdin to a file prior and not
* making any changes to the environment.
*
* Author: Grok
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void print_help(const char *progname) {
printf("Usage: %s [options] command [args...]\n", progname);
printf("Run a command with optional redirected output/input, preserving the environment.\n\n");
printf("Options:\n");
printf(" --stdout <file> Redirect stdout to <file>\n");
printf(" --stderr <file> Redirect stderr to <file>\n");
printf(" --stdin <file> Redirect stdin from <file>\n");
printf(" --help Show this help message and exit\n");
printf("\nNotes:\n");
printf(" - Options are optional and must appear before the command or '--'.\n");
printf(" - If an option is not specified, the corresponding stream is not redirected.\n");
printf(" - Use '--' to separate options from arguments that look like options.\n");
printf("Example:\n");
printf(" %s --stdout out.txt --stderr err.txt /bin/echo hello\n", progname);
exit(0);
}
int main(int argc, char *argv[], char *envp[]) {
const char *stdout_file = NULL;
const char *stderr_file = NULL;
const char *stdin_file = NULL;
int i;
int cmd_start = 0;
// Parse arguments
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) {
print_help(argv[0]);
} else if (strcmp(argv[i], "--") == 0) {
cmd_start = i + 1;
break;
} else if (strcmp(argv[i], "--stdout") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --stdout requires a file argument\n");
return 1;
}
stdout_file = argv[++i];
} else if (strcmp(argv[i], "--stderr") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --stderr requires a file argument\n");
return 1;
}
stderr_file = argv[++i];
} else if (strcmp(argv[i], "--stdin") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --stdin requires a file argument\n");
return 1;
}
stdin_file = argv[++i];
} else {
// Non-option argument; start of command
cmd_start = i;
break;
}
}
// Check if a command was provided
if (cmd_start == 0 || cmd_start >= argc) {
fprintf(stderr, "No command specified\n");
return 1;
}
// Redirect stdin if specified
if (stdin_file) {
int stdin_fd = open(stdin_file, O_RDONLY);
if (stdin_fd == -1) {
perror("Failed to open stdin file");
return 1;
}
if (dup2(stdin_fd, STDIN_FILENO) == -1) {
perror("Failed to redirect stdin");
return 1;
}
close(stdin_fd);
}
// Redirect stdout if specified
if (stdout_file) {
int stdout_fd = open(stdout_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (stdout_fd == -1) {
perror("Failed to open stdout file");
return 1;
}
if (dup2(stdout_fd, STDOUT_FILENO) == -1) {
perror("Failed to redirect stdout");
return 1;
}
close(stdout_fd);
}
// Redirect stderr if specified
if (stderr_file) {
int stderr_fd = open(stderr_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (stderr_fd == -1) {
perror("Failed to open stderr file");
return 1;
}
if (dup2(stderr_fd, STDERR_FILENO) == -1) {
perror("Failed to redirect stderr");
return 1;
}
close(stderr_fd);
}
// Detach from terminal
if (setsid() == -1) {
perror("Failed to detach from terminal");
return 1;
}
// Execute the command with the inherited environment
execvpe(argv[cmd_start], &argv[cmd_start], envp);
perror("Failed to execute command");
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment