Skip to content

Instantly share code, notes, and snippets.

@jpalala
Last active September 14, 2025 00:39
Show Gist options
  • Save jpalala/138277dfd90c3eccc5da25621a2b515e to your computer and use it in GitHub Desktop.
Save jpalala/138277dfd90c3eccc5da25621a2b515e to your computer and use it in GitHub Desktop.
DEXEC.SH (NOW DEXEC IN C)

dexec

dexec is a small command‑line utility (written in C) that wraps Docker commands to make working with Docker Compose and docker exec easier. By default it runs docker compose, but when called with the -e (or --exec) flag, it runs docker exec.


Features

  • Checks that the docker binary exists before doing anything.
  • Default behavior: use docker compose with any arguments you pass.
  • Exec mode: when you pass -e (or --exec), it calls docker exec with the given container name and command.
  • Interactive TTY support for exec mode (‑it).

Installation / Build

  1. Make sure you have a C compiler (e.g. gcc) installed.

  2. Clone or copy the source file dexec.c into your project directory.

  3. Build with:

    gcc -o dexec dexec.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// dexec: if you execute this program with -e, docker exec will get called.
// Otherwise, docker compose will be called by default.
// This version also checks that "docker" exists before doing anything.
// compile: gcc -o dexec dexec.c
void print_usage(const char *prog) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s [--exec | -e] <container> <command> [args...]\n", prog);
fprintf(stderr, " %s [other docker compose args...]\n", prog);
}
// Check if a command exists in PATH: here to check whether "docker" is present
int command_exists(const char *cmd) {
char *path_env = getenv("PATH");
if (path_env == NULL) {
return 0;
}
// duplicate because strtok modifies
char *paths = strdup(path_env);
if (!paths) {
return 0;
}
char *saveptr = NULL;
char *dir = strtok_r(paths, ":", &saveptr);
while (dir != NULL) {
size_t len = strlen(dir) + 1 + strlen(cmd) + 1;
char *full = malloc(len);
if (!full) {
free(paths);
return 0;
}
snprintf(full, len, "%s/%s", dir, cmd);
if (access(full, X_OK) == 0) {
free(full);
free(paths);
return 1; // found
}
free(full);
dir = strtok_r(NULL, ":", &saveptr);
}
free(paths);
return 0; // not found
}
int main(int argc, char *argv[]) {
int opt;
int exec_mode = 0;
// First, check that docker exists
if (!command_exists("docker")) {
fprintf(stderr, "Error: \"docker\" command not found in PATH. Please install Docker or make sure PATH is correct.\n");
exit(EXIT_FAILURE);
}
// parse -e / --exec
while ((opt = getopt(argc, argv, "e-:h")) != -1) {
switch (opt) {
case 'e':
exec_mode = 1;
break;
case '-':
// long options parsing for --exec or --help
if (strcmp(optarg, "exec") == 0) {
exec_mode = 1;
} else if (strcmp(optarg, "help") == 0) {
print_usage(argv[0]);
exit(EXIT_SUCCESS);
} else {
fprintf(stderr, "Unknown option --%s\n", optarg);
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
break;
case 'h':
print_usage(argv[0]);
exit(EXIT_SUCCESS);
case '?':
default:
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
}
if (exec_mode) {
// after -e, expect at least container and command
if (optind + 1 >= argc) {
fprintf(stderr, "Error: -e requires container and command\n");
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
char *container = argv[optind];
char *cmd = argv[optind+1];
char **cmd_args = &argv[optind+1]; // command + its args
// Build argument list for execvp
// e.g. docker exec -it <container> <cmd> <args...>
// You might want interactivity etc, but start simple:
int num_args = argc - (optind+1) + 4;
char **args = malloc((num_args + 1) * sizeof(char *));
if (!args) {
perror("malloc");
exit(EXIT_FAILURE);
}
args[0] = "docker";
args[1] = "exec";
args[2] = "-it"; // optional, only if you want TTY; maybe make this configurable
args[3] = container;
for (int i = 0; i + optind + 1 < argc; i++) {
args[4 + i] = argv[optind + 1 + i];
}
args[4 + (argc - (optind+1))] = NULL;
// execute
execvp("docker", args);
// if execvp returns, error
perror("execvp failed");
free(args);
exit(EXIT_FAILURE);
} else {
// compose/default mode
// pass all arguments to docker compose
// e.g. docker compose up <args...> or maybe allow arbitrary compose subcommand
// For example, run `docker compose up` first, or allow specifying subcommand
// Let's assume default is up
// Or check if first non-option arg is a compose subcommand
// Prepare arguments: "docker", "compose", <args...>
int num_args = (argc - optind) + 2 + 1;
char **args = malloc((num_args + 1) * sizeof(char *));
if (!args) {
perror("malloc");
exit(EXIT_FAILURE);
}
args[0] = "docker";
args[1] = "compose";
// if no args left (argc==optind), maybe default subcommand
int offset = 2;
if (optind < argc) {
for (int i = 0; i < argc - optind; i++) {
args[offset + i] = argv[optind + i];
}
offset += argc - optind;
} else {
// no extra args given, default to "up"
args[offset++] = "up";
}
args[offset] = NULL;
execvp("docker", args);
perror("execvp failed");
free(args);
exit(EXIT_FAILURE);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment