From fb18c82c56fc23e03337ad5ac53142379c9d5120 Mon Sep 17 00:00:00 2001 From: benjaminmueggenburg-serato Date: Mon, 11 May 2026 08:30:25 +1200 Subject: [PATCH 1/2] env: add bash-style default value operator :- Signed-off-by: benjaminmueggenburg-serato --- src/flb_env.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/flb_env.c b/src/flb_env.c index 6079a3b7cbf..5293f01aea8 100644 --- a/src/flb_env.c +++ b/src/flb_env.c @@ -311,6 +311,9 @@ const char *flb_env_get(struct flb_env *env, const char *key) /* * Given a 'value', lookup for variables, if found, return a new composed * sds string. + * + * Supports bash-style default substitution inside ${...}: + * ${name:-word} — if unset or empty, expand word; do not assign. */ flb_sds_t flb_env_var_translate(struct flb_env *env, const char *value) { @@ -321,6 +324,9 @@ flb_sds_t flb_env_var_translate(struct flb_env *env, const char *value) int pre_var; int have_var = FLB_FALSE; const char *env_var = NULL; + char *v_sep; + const char *def_val; + int def_len; char *v_start = NULL; char *v_end = NULL; char tmp[4096]; @@ -358,6 +364,18 @@ flb_sds_t flb_env_var_translate(struct flb_env *env, const char *value) strncpy(tmp, v_start, v_len); tmp[v_len] = '\0'; have_var = FLB_TRUE; + + /* Bash-style default value expansion :- */ + v_sep = strstr(tmp, ":"); + def_val = NULL; + def_len = 0; + + if (v_sep && (v_sep[1] == '-')) { + def_val = v_sep + 2; + def_len = strlen(def_val); + *v_sep = '\0'; + } + /* Append pre-variable content */ pre_var = (v_start - 2) - (value + i); @@ -374,7 +392,8 @@ flb_sds_t flb_env_var_translate(struct flb_env *env, const char *value) /* Lookup the variable in our env-hash */ env_var = flb_env_get(env, tmp); - if (env_var) { + /* Skip env if it's empty and have a fallback defined */ + if (env_var && !(def_val && strlen(env_var) == 0)) { e_len = strlen(env_var); s = buf_append(buf, env_var, e_len); if (!s) { @@ -385,6 +404,16 @@ flb_sds_t flb_env_var_translate(struct flb_env *env, const char *value) buf = s; } } + else if (def_val) { + if (def_len > 0) { + s = buf_append(buf, def_val, def_len); + if (!s) { + flb_sds_destroy(buf); + return NULL; + } + buf = s; + } + } else if (env->warn_unused == FLB_TRUE) { flb_warn("[env] variable ${%s} is used but not set", tmp); } From 93acbd7f14a4f0a5b3b1f94c95058b0e388ba95f Mon Sep 17 00:00:00 2001 From: benjaminmueggenburg-serato Date: Mon, 11 May 2026 08:35:45 +1200 Subject: [PATCH 2/2] tests: internal: env: Add test cases for default value operator :- Signed-off-by: benjaminmueggenburg-serato --- tests/internal/env.c | 155 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/tests/internal/env.c b/tests/internal/env.c index f41e775502a..e28c6e7ae60 100644 --- a/tests/internal/env.c +++ b/tests/internal/env.c @@ -413,6 +413,158 @@ void test_file_env_var_missing() flb_env_destroy(env); } +/* ${name:-word} when unset or empty */ +void test_expand_default_hyphen() +{ + struct flb_env *env; + flb_sds_t buf = NULL; + const char *v; + int ret; + + env = flb_env_create(); + if (!TEST_CHECK(env != NULL)) { + TEST_MSG("flb_env_create failed"); + return; + } + + buf = flb_env_var_translate(env, "${FLB_IT_DEFHYPH_Z:-fallback}"); + if (!TEST_CHECK(buf != NULL)) { + TEST_MSG("translate failed"); + flb_env_destroy(env); + return; + } + if (!TEST_CHECK(strcmp(buf, "fallback") == 0)) { + TEST_MSG("expected fallback, got=%s", buf); + } + flb_sds_destroy(buf); + + v = flb_env_get(env, "FLB_IT_DEFHYPH_Z"); + if (!TEST_CHECK(v == NULL)) { + TEST_MSG(":- must not assign locally"); + } + + ret = flb_env_set(env, "FLB_IT_DEFHYPH_Z", ""); + if (!TEST_CHECK(ret >= 0)) { + TEST_MSG("flb_env_set empty failed"); + flb_env_destroy(env); + return; + } + buf = flb_env_var_translate(env, "${FLB_IT_DEFHYPH_Z:-empty_fallback}"); + if (!TEST_CHECK(buf != NULL)) { + TEST_MSG("translate empty failed"); + flb_env_destroy(env); + return; + } + if (!TEST_CHECK(strcmp(buf, "empty_fallback") == 0)) { + TEST_MSG("empty local should use default, got=%s", buf); + } + flb_sds_destroy(buf); + flb_env_destroy(env); +} + + +/* ${name:-} should return an empty string if name is unset or empty */ +void test_expand_empty_default() +{ + struct flb_env *env; + flb_sds_t buf = NULL; + int ret; + + env = flb_env_create(); + if (!TEST_CHECK(env != NULL)) { + TEST_MSG("flb_env_create failed"); + return; + } + + /* Case 1: Variable is unset */ + buf = flb_env_var_translate(env, "val=${VAR_NOT_SET:-}"); + if (!TEST_CHECK(buf != NULL)) { + TEST_MSG("translate failed"); + flb_env_destroy(env); + return; + } + if (!TEST_CHECK(strcmp(buf, "val=") == 0)) { + TEST_MSG("expected 'val=', got='%s'", buf); + } + flb_sds_destroy(buf); + + /* Case 2: Variable is explicitly set to empty string */ + ret = flb_env_set(env, "VAR_EMPTY", ""); + if (!TEST_CHECK(ret >= 0)) { + TEST_MSG("flb_env_set failed"); + flb_env_destroy(env); + return; + } + + buf = flb_env_var_translate(env, "${VAR_EMPTY:-}"); + if (!TEST_CHECK(buf != NULL)) { + TEST_MSG("translate failed"); + flb_env_destroy(env); + return; + } + + /* Result should be length 0 */ + if (!TEST_CHECK(strlen(buf) == 0)) { + TEST_MSG("expected empty string, got='%s'", buf); + } + + flb_sds_destroy(buf); + flb_env_destroy(env); +} + + +void test_expand_default_hyphen_from_os() +{ + struct flb_env *env; + flb_sds_t buf = NULL; + char *var_name = "FLB_IT_DEFHYPH_OS"; + char *var_val = "from_process"; + int ret; + + /* Set OS environment inline */ + #ifdef FLB_SYSTEM_WINDOWS + ret = _putenv_s(var_name, var_val); + #else + ret = setenv(var_name, var_val, 1); + #endif + + if (!TEST_CHECK(ret == 0)) { + TEST_MSG("putenv failed"); + return; + } + + env = flb_env_create(); + if (!TEST_CHECK(env != NULL)) { + TEST_MSG("flb_env_create failed"); + #ifdef FLB_SYSTEM_WINDOWS + _putenv_s(var_name, ""); + #else + unsetenv(var_name); + #endif + return; + } + + buf = flb_env_var_translate(env, "${FLB_IT_DEFHYPH_OS:-fallback}"); + if (!TEST_CHECK(buf != NULL)) { + TEST_MSG("translate failed"); + } + else { + if (!TEST_CHECK(strcmp(buf, var_val) == 0)) { + TEST_MSG("expected from_process, got=%s", buf); + } + flb_sds_destroy(buf); + } + + flb_env_destroy(env); + + /* Unset OS environment inline */ + #ifdef FLB_SYSTEM_WINDOWS + _putenv_s(var_name, ""); + #else + unsetenv(var_name); + #endif +} + TEST_LIST = { { "translate_long_env" , test_translate_long_env}, @@ -421,5 +573,8 @@ TEST_LIST = { { "file_env_var_uri" , test_file_env_var_uri}, { "mixed_env_vars" , test_mixed_env_vars}, { "file_env_var_missing" , test_file_env_var_missing}, + { "expand_default_hyphen" , test_expand_default_hyphen}, + { "expand_empty_default", test_expand_empty_default}, + { "expand_default_hyphen_from_os", test_expand_default_hyphen_from_os}, { NULL, NULL } };