summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/environment.d.xml16
-rw-r--r--src/basic/env-util.c67
-rw-r--r--src/basic/env-util.h1
-rw-r--r--src/basic/fileio.c6
-rw-r--r--src/test/test-env-util.c14
-rw-r--r--src/test/test-fileio.c16
6 files changed, 108 insertions, 12 deletions
diff --git a/man/environment.d.xml b/man/environment.d.xml
index 4022c25c36..be7758a2f9 100644
--- a/man/environment.d.xml
+++ b/man/environment.d.xml
@@ -78,8 +78,16 @@
<literal><replaceable>KEY</replaceable>=<replaceable>VALUE</replaceable></literal> environment
variable assignments, separated by newlines. The right hand side of these assignments may
reference previously defined environment variables, using the <literal>${OTHER_KEY}</literal>
- and <literal>$OTHER_KEY</literal> format. No other elements of shell syntax are supported.
- </para>
+ and <literal>$OTHER_KEY</literal> format. It is also possible to use
+
+ <literal>${<replaceable>FOO</replaceable>:-<replaceable>DEFAULT_VALUE</replaceable>}</literal>
+ to expand in the same way as <literal>${<replaceable>FOO</replaceable>}</literal> unless the
+ expansion would be empty, in which case it expands to <replaceable>DEFAULT_VALUE</replaceable>,
+ and use
+ <literal>${<replaceable>FOO</replaceable>:+<replaceable>ALTERNATE_VALUE</replaceable>}</literal>
+ to expand to <replaceable>ALTERNATE_VALUE</replaceable> as long as
+ <literal>${<replaceable>FOO</replaceable>}</literal> would have expanded to a non-empty value.
+ No other elements of shell syntax are supported.</para>
<para>Each<replaceable>KEY</replaceable> must be a valid variable name. Empty lines
and lines beginning with the comment character <literal>#</literal> are ignored.</para>
@@ -95,8 +103,8 @@
<programlisting>
FOO_DEBUG=force-software-gl,log-verbose
PATH=/opt/foo/bin:$PATH
- LD_LIBRARY_PATH=/opt/foo/lib
- XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS}
+ LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}/opt/foo/lib
+ XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}
</programlisting>
</example>
</refsect2>
diff --git a/src/basic/env-util.c b/src/basic/env-util.c
index 1b955ff1d5..2ca64c3301 100644
--- a/src/basic/env-util.c
+++ b/src/basic/env-util.c
@@ -525,12 +525,16 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
CURLY,
VARIABLE,
VARIABLE_RAW,
+ TEST,
+ DEFAULT_VALUE,
+ ALTERNATE_VALUE,
} state = WORD;
- const char *e, *word = format;
+ const char *e, *word = format, *test_value;
char *k;
_cleanup_free_ char *r = NULL;
- size_t i;
+ size_t i, len;
+ int nest = 0;
assert(format);
@@ -554,7 +558,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
word = e-1;
state = VARIABLE;
-
+ nest++;
} else if (*e == '$') {
k = strnappend(r, word, e-word);
if (!k)
@@ -596,6 +600,63 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
word = e+1;
state = WORD;
+ } else if (*e == ':') {
+ if (!(flags & REPLACE_ENV_ALLOW_EXTENDED))
+ /* Treat this as unsupported syntax, i.e. do no replacement */
+ state = WORD;
+ else {
+ len = e-word-2;
+ state = TEST;
+ }
+ }
+ break;
+
+ case TEST:
+ if (*e == '-')
+ state = DEFAULT_VALUE;
+ else if (*e == '+')
+ state = ALTERNATE_VALUE;
+ else {
+ state = WORD;
+ break;
+ }
+
+ test_value = e+1;
+ break;
+
+ case DEFAULT_VALUE: /* fall through */
+ case ALTERNATE_VALUE:
+ assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
+
+ if (*e == '{') {
+ nest++;
+ break;
+ }
+
+ if (*e != '}')
+ break;
+
+ nest--;
+ if (nest == 0) { // || !strchr(e+1, '}')) {
+ const char *t;
+ _cleanup_free_ char *v = NULL;
+
+ t = strv_env_get_n(env, word+2, len, flags);
+
+ if (t && state == ALTERNATE_VALUE)
+ t = v = replace_env_n(test_value, e-test_value, env, flags);
+ else if (!t && state == DEFAULT_VALUE)
+ t = v = replace_env_n(test_value, e-test_value, env, flags);
+
+ k = strappend(r, t);
+ if (!k)
+ return NULL;
+
+ free(r);
+ r = k;
+
+ word = e+1;
+ state = WORD;
}
break;
diff --git a/src/basic/env-util.h b/src/basic/env-util.h
index 43a1371f5e..e88fa6aac0 100644
--- a/src/basic/env-util.h
+++ b/src/basic/env-util.h
@@ -32,6 +32,7 @@ bool env_assignment_is_valid(const char *e);
enum {
REPLACE_ENV_USE_ENVIRONMENT = 1u,
REPLACE_ENV_ALLOW_BRACELESS = 2u,
+ REPLACE_ENV_ALLOW_EXTENDED = 4u,
};
char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index 8185f67e00..b9a9f74892 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -784,7 +784,9 @@ static int merge_env_file_push(
}
expanded_value = replace_env(value, *env,
- REPLACE_ENV_USE_ENVIRONMENT|REPLACE_ENV_ALLOW_BRACELESS);
+ REPLACE_ENV_USE_ENVIRONMENT|
+ REPLACE_ENV_ALLOW_BRACELESS|
+ REPLACE_ENV_ALLOW_EXTENDED);
if (!expanded_value)
return -ENOMEM;
@@ -799,7 +801,7 @@ int merge_env_file(
const char *fname) {
/* NOTE: this function supports braceful and braceless variable expansions,
- * unlike other exported parsing functions.
+ * plus "extended" substitutions, unlike other exported parsing functions.
*/
return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL);
diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c
index 77a5219d82..dfcd9cb724 100644
--- a/src/test/test-env-util.c
+++ b/src/test/test-env-util.c
@@ -185,6 +185,12 @@ static void test_replace_env_argv(void) {
"${FOO",
"FOO$$${FOO}",
"$$FOO${FOO}",
+ "${FOO:-${BAR}}",
+ "${QUUX:-${FOO}}",
+ "${FOO:+${BAR}}",
+ "${QUUX:+${BAR}}",
+ "${FOO:+|${BAR}|}}",
+ "${FOO:+|${BAR}{|}",
NULL
};
_cleanup_strv_free_ char **r = NULL;
@@ -202,7 +208,13 @@ static void test_replace_env_argv(void) {
assert_se(streq(r[8], "${FOO"));
assert_se(streq(r[9], "FOO$BAR BAR"));
assert_se(streq(r[10], "$FOOBAR BAR"));
- assert_se(strv_length(r) == 11);
+ assert_se(streq(r[11], "${FOO:-waldo}"));
+ assert_se(streq(r[12], "${QUUX:-BAR BAR}"));
+ assert_se(streq(r[13], "${FOO:+waldo}"));
+ assert_se(streq(r[14], "${QUUX:+waldo}"));
+ assert_se(streq(r[15], "${FOO:+|waldo|}}"));
+ assert_se(streq(r[16], "${FOO:+|waldo{|}"));
+ assert_se(strv_length(r) == 17);
}
static void test_env_clean(void) {
diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c
index b117335db8..b1d688c89e 100644
--- a/src/test/test-fileio.c
+++ b/src/test/test-fileio.c
@@ -229,6 +229,10 @@ static void test_merge_env_file(void) {
"twentytwo=2${one}\n"
"xxx_minus_three=$xxx - 3\n"
"xxx=0x$one$one$one\n"
+ "yyy=${one:-fallback}\n"
+ "zzz=${one:+replacement}\n"
+ "zzzz=${foobar:-${nothing}}\n"
+ "zzzzz=${nothing:+${nothing}}\n"
, false);
assert(r >= 0);
@@ -245,7 +249,11 @@ static void test_merge_env_file(void) {
assert_se(streq(a[3], "twentytwo=22"));
assert_se(streq(a[4], "xxx=0x222"));
assert_se(streq(a[5], "xxx_minus_three= - 3"));
- assert_se(a[6] == NULL);
+ assert_se(streq(a[6], "yyy=2"));
+ assert_se(streq(a[7], "zzz=replacement"));
+ assert_se(streq(a[8], "zzzz="));
+ assert_se(streq(a[9], "zzzzz="));
+ assert_se(a[10] == NULL);
r = merge_env_file(&a, NULL, t);
assert_se(r >= 0);
@@ -260,7 +268,11 @@ static void test_merge_env_file(void) {
assert_se(streq(a[3], "twentytwo=22"));
assert_se(streq(a[4], "xxx=0x222"));
assert_se(streq(a[5], "xxx_minus_three=0x222 - 3"));
- assert_se(a[6] == NULL);
+ assert_se(streq(a[6], "yyy=2"));
+ assert_se(streq(a[7], "zzz=replacement"));
+ assert_se(streq(a[8], "zzzz="));
+ assert_se(streq(a[9], "zzzzz="));
+ assert_se(a[10] == NULL);
}
static void test_merge_env_file_invalid(void) {