mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-02 14:01:20 -03:00
Merge branch 'splitenv_1.8'
This merges support for PATH variables. Closes #5245
This commit is contained in:
@@ -11,6 +11,7 @@ fish 3.0 is a major release which brings with it both improvements in functional
|
||||
|
||||
## Notable non-backward compatible changes
|
||||
- `%` is no longer used for process and job expansion, except for `%self` which is retained. Some commands have been wrapped to still understand process expansion, including `bg`, `fg` and `kill` (#4230, #1202)
|
||||
- Incoming environment variables are no longer split into arrays based on RS. Instead, variables are not split, unless their name ends in PATH, in which case they are split on colons. (#436)
|
||||
- A literal `{}` now expands to itself, rather than nothing. This makes working with `find -exec` easier. (#1109, #4632)
|
||||
- Successive commas in brace expansions are handled in less surprising manner (`{,,,}` expands to four empty strings rather than an empty string, a comma and an empty string again). (#3002, #4632).
|
||||
- `for` loop control variables are no longer local to the `for` block (#1935).
|
||||
|
||||
@@ -848,7 +848,36 @@ A range of indices can be specified, see <a href='#expand-index-range'>index ran
|
||||
|
||||
All arrays are one-dimensional and cannot contain other arrays, although it is possible to fake nested arrays using the dereferencing rules of <a href="#expand-variable">variable expansion</a>.
|
||||
|
||||
`fish` automatically creates arrays from the variables `PATH`, `CDPATH` and `MANPATH` when it is started. (Previous versions created arrays from *all* colon-delimited environment variables.)
|
||||
When an array is exported as an environment variable, it is either space or colon delimited, depending on whether it is a path variable:
|
||||
\fish
|
||||
set -x smurf blue small
|
||||
set -x smurf_PATH forest mushroom
|
||||
env | grep smurf
|
||||
<outp>
|
||||
# smurf=blue small
|
||||
# smurf_PATH=forest:mushroom
|
||||
</outp>
|
||||
\endfish
|
||||
|
||||
`fish` automatically creates arrays from all environment variables whose name ends in PATH, by splitting them on colons. Other variables are not automatically split.
|
||||
|
||||
\subsection variables-path PATH variables
|
||||
|
||||
Path variables are a special kind of variable used to support colon-delimited path lists including PATH, CDPATH, MANPATH, PYTHONPATH, etc. All variables that end in `PATH` (case-sensitive) become PATH variables.
|
||||
|
||||
PATH variables act as normal arrays, except they are are implicitly joined and split on colons.
|
||||
\fish
|
||||
set MYPATH 1 2 3
|
||||
echo "$MYPATH"
|
||||
<outp># 1:2:3</outp>
|
||||
set MYPATH "$MYPATH:4:5"
|
||||
echo $MYPATH
|
||||
# 1 2 3 4 5
|
||||
echo "$MYPATH"
|
||||
<outp># 1:2:3:4:5</outp>
|
||||
\endfish
|
||||
|
||||
Variables can be marked or unmarked as PATH variables via the `--path` and `--unpath` options to `set`.
|
||||
|
||||
\subsection variables-special Special variables
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ struct set_cmd_opts_t {
|
||||
bool erase = false;
|
||||
bool list = false;
|
||||
bool unexport = false;
|
||||
bool pathvar = false;
|
||||
bool unpathvar = false;
|
||||
bool universal = false;
|
||||
bool query = false;
|
||||
bool shorten_ok = true;
|
||||
@@ -45,6 +47,12 @@ struct set_cmd_opts_t {
|
||||
bool preserve_failure_exit_status = true;
|
||||
};
|
||||
|
||||
/// Values used for long-only options.
|
||||
enum {
|
||||
opt_path = 1,
|
||||
opt_unpath = 2,
|
||||
};
|
||||
|
||||
// Variables used for parsing the argument list. This command is atypical in using the "+"
|
||||
// (REQUIRE_ORDER) option for flag parsing. This is not typical of most fish commands. It means
|
||||
// we stop scanning for flags when the first non-flag argument is seen.
|
||||
@@ -56,6 +64,7 @@ static const struct woption long_options[] = {
|
||||
{L"universal", no_argument, NULL, 'U'}, {L"long", no_argument, NULL, 'L'},
|
||||
{L"query", no_argument, NULL, 'q'}, {L"show", no_argument, NULL, 'S'},
|
||||
{L"append", no_argument, NULL, 'a'}, {L"prepend", no_argument, NULL, 'p'},
|
||||
{L"path", no_argument, NULL, opt_path}, {L"unpath", no_argument, NULL, opt_unpath},
|
||||
{L"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}};
|
||||
|
||||
// Hint for invalid path operation with a colon.
|
||||
@@ -122,6 +131,14 @@ static int parse_cmd_opts(set_cmd_opts_t &opts, int *optind, //!OCLINT(high ncs
|
||||
opts.unexport = true;
|
||||
break;
|
||||
}
|
||||
case opt_path: {
|
||||
opts.pathvar = true;
|
||||
break;
|
||||
}
|
||||
case opt_unpath: {
|
||||
opts.unpathvar = true;
|
||||
break;
|
||||
}
|
||||
case 'U': {
|
||||
opts.universal = true;
|
||||
break;
|
||||
@@ -184,6 +201,13 @@ static int validate_cmd_opts(const wchar_t *cmd, set_cmd_opts_t &opts, //!OCLIN
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
// Variables can only have one path status.
|
||||
if (opts.pathvar && opts.unpathvar) {
|
||||
streams.err.append_format(BUILTIN_ERR_EXPUNEXP, cmd);
|
||||
builtin_print_help(parser, streams, cmd, streams.err);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
// Trying to erase and (un)export at the same time doesn't make sense.
|
||||
if (opts.erase && (opts.exportv || opts.unexport)) {
|
||||
streams.err.append_format(BUILTIN_ERR_COMBO, cmd);
|
||||
@@ -429,13 +453,15 @@ static void erase_values(wcstring_list_t &list, const std::vector<long> &indexes
|
||||
}
|
||||
}
|
||||
|
||||
static int compute_scope(set_cmd_opts_t &opts) {
|
||||
static env_mode_flags_t compute_scope(set_cmd_opts_t &opts) {
|
||||
int scope = ENV_USER;
|
||||
if (opts.local) scope |= ENV_LOCAL;
|
||||
if (opts.global) scope |= ENV_GLOBAL;
|
||||
if (opts.exportv) scope |= ENV_EXPORT;
|
||||
if (opts.unexport) scope |= ENV_UNEXPORT;
|
||||
if (opts.universal) scope |= ENV_UNIVERSAL;
|
||||
if (opts.pathvar) scope |= ENV_PATHVAR;
|
||||
if (opts.unpathvar) scope |= ENV_UNPATHVAR;
|
||||
return scope;
|
||||
}
|
||||
|
||||
|
||||
@@ -1899,7 +1899,18 @@ wcstring_list_t split_string(const wcstring &val, wchar_t sep) {
|
||||
}
|
||||
|
||||
wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) {
|
||||
if (vals.empty()) return wcstring{};
|
||||
|
||||
// Reserve the size we will need.
|
||||
// count-1 separators, plus the length of all strings.
|
||||
size_t size = vals.size() - 1;
|
||||
for (const wcstring &s : vals) {
|
||||
size += s.size();
|
||||
}
|
||||
|
||||
// Construct the string.
|
||||
wcstring result;
|
||||
result.reserve(size);
|
||||
bool first = true;
|
||||
for (const wcstring &s : vals) {
|
||||
if (!first) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
@@ -301,6 +302,13 @@ bool contains(const Col &col, const T2 &val) {
|
||||
return std::find(std::begin(col), std::end(col), val) != std::end(col);
|
||||
}
|
||||
|
||||
/// Append a vector \p donator to the vector \p receiver.
|
||||
template <typename T>
|
||||
void vec_append(std::vector<T> &receiver, std::vector<T> &&donator) {
|
||||
receiver.insert(receiver.end(), std::make_move_iterator(donator.begin()),
|
||||
std::make_move_iterator(donator.end()));
|
||||
}
|
||||
|
||||
/// Print a stack trace to stderr.
|
||||
void show_stackframe(const wchar_t msg_level, int frame_count = 100, int skip_levels = 0);
|
||||
|
||||
|
||||
97
src/env.cpp
97
src/env.cpp
@@ -76,6 +76,10 @@ extern char **environ;
|
||||
#define READ_BYTE_LIMIT 10 * 1024 * 1024
|
||||
size_t read_byte_limit = READ_BYTE_LIMIT;
|
||||
|
||||
/// The character used to delimit path and non-path variables in exporting and in string expansion.
|
||||
static const wchar_t PATH_ARRAY_SEP = L':';
|
||||
static const wchar_t NONPATH_ARRAY_SEP = L' ';
|
||||
|
||||
bool g_use_posix_spawn = false; // will usually be set to true
|
||||
bool curses_initialized = false;
|
||||
|
||||
@@ -298,20 +302,9 @@ static bool is_read_only(const wchar_t *val) {
|
||||
|
||||
static bool is_read_only(const wcstring &val) { return is_read_only(val.c_str()); }
|
||||
|
||||
// Here is the whitelist of variables that we colon-delimit, both incoming from the environment and
|
||||
// outgoing back to it. This is deliberately very short - we don't want to add language-specific
|
||||
// values like CLASSPATH.
|
||||
static const string_set_t colon_delimited_variable = {L"PATH", L"CDPATH", L"MANPATH"};
|
||||
static bool variable_is_colon_delimited_var(const wchar_t *str) {
|
||||
/// List of "path" like variable names that need special handling. This includes automatic
|
||||
/// splitting and joining on import/export. As well as replacing empty elements, which
|
||||
/// implicitly refer to the CWD, with an explicit '.' in the case of PATH and CDPATH. Note this
|
||||
/// is sorted
|
||||
return string_set_contains(colon_delimited_variable, str);
|
||||
}
|
||||
|
||||
static bool variable_is_colon_delimited_var(const wcstring &str) {
|
||||
return variable_is_colon_delimited_var(str.c_str());
|
||||
/// Return true if a variable should become a path variable by default. See #436.
|
||||
static bool variable_should_auto_pathvar(const wcstring &name) {
|
||||
return string_suffixes_string(L"PATH", name);
|
||||
}
|
||||
|
||||
/// Table of variables whose value is dynamically calculated, such as umask, status, etc.
|
||||
@@ -353,9 +346,6 @@ static void handle_timezone(const wchar_t *env_var_name) {
|
||||
/// Unfortunately that convention causes problems for fish scripts. So this function replaces the
|
||||
/// empty path element with an explicit ".". See issue #3914.
|
||||
static void fix_colon_delimited_var(const wcstring &var_name) {
|
||||
// While we auto split/join MANPATH we do not want to replace empty elements with "." (#4158).
|
||||
if (var_name == L"MANPATH") return;
|
||||
|
||||
const auto paths = env_get(var_name);
|
||||
if (paths.missing_or_empty()) return;
|
||||
|
||||
@@ -527,9 +517,9 @@ static bool initialize_curses_using_fallback(const char *term) {
|
||||
/// Ensure the content of the magic path env vars is reasonable. Specifically, that empty path
|
||||
/// elements are converted to explicit "." to make the vars easier to use in fish scripts.
|
||||
static void init_path_vars() {
|
||||
for (const wchar_t *var_name : colon_delimited_variable) {
|
||||
fix_colon_delimited_var(var_name);
|
||||
}
|
||||
// Do not replace empties in MATHPATH - see #4158.
|
||||
fix_colon_delimited_var(L"PATH");
|
||||
fix_colon_delimited_var(L"CDPATH");
|
||||
}
|
||||
|
||||
/// Update the value of g_guessed_fish_emoji_width
|
||||
@@ -847,10 +837,8 @@ static void setup_var_dispatch_table() {
|
||||
var_dispatch_table.emplace(var_name, handle_curses_change);
|
||||
}
|
||||
|
||||
for (const auto &var_name : colon_delimited_variable) {
|
||||
var_dispatch_table.emplace(var_name, handle_magic_colon_var_change);
|
||||
}
|
||||
|
||||
var_dispatch_table.emplace(L"PATH", handle_magic_colon_var_change);
|
||||
var_dispatch_table.emplace(L"CDPATH", handle_magic_colon_var_change);
|
||||
var_dispatch_table.emplace(L"fish_term256", handle_fish_term_change);
|
||||
var_dispatch_table.emplace(L"fish_term24bit", handle_fish_term_change);
|
||||
var_dispatch_table.emplace(L"fish_escape_delay_ms", handle_escape_delay_change);
|
||||
@@ -885,10 +873,9 @@ void env_init(const struct config_paths_t *paths /* or NULL */) {
|
||||
env_set_empty(key_and_val, ENV_EXPORT | ENV_GLOBAL);
|
||||
} else {
|
||||
key.assign(key_and_val, 0, eql);
|
||||
val.assign(key_and_val, eql+1, wcstring::npos);
|
||||
if (is_read_only(key) || is_electric(key)) continue;
|
||||
val.assign(key_and_val, eql + 1, wcstring::npos);
|
||||
wchar_t sep = variable_is_colon_delimited_var(key) ? L':' : ARRAY_SEP;
|
||||
env_set(key, ENV_EXPORT | ENV_GLOBAL, split_string(val, sep));
|
||||
env_set(key, ENV_EXPORT | ENV_GLOBAL, {val});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,8 +1043,10 @@ static int set_umask(const wcstring_list_t &list_val) {
|
||||
/// * ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric
|
||||
/// variables set from the local or universal scopes, or set as exported.
|
||||
/// * ENV_INVALID, the variable value was invalid. This applies only to special variables.
|
||||
static int env_set_internal(const wcstring &key, env_mode_flags_t var_mode, wcstring_list_t val) {
|
||||
static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode,
|
||||
wcstring_list_t val) {
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
env_mode_flags_t var_mode = input_var_mode;
|
||||
bool has_changed_old = vars_stack().has_changed_exported;
|
||||
int done = 0;
|
||||
|
||||
@@ -1109,13 +1098,12 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t var_mode, wcst
|
||||
// Determine the node.
|
||||
bool has_changed_new = false;
|
||||
env_node_t *preexisting_node = env_get_node(key);
|
||||
bool preexisting_entry_exportv = false;
|
||||
maybe_t<env_var_t::env_var_flags_t> preexisting_flags{};
|
||||
if (preexisting_node != NULL) {
|
||||
var_table_t::const_iterator result = preexisting_node->env.find(key);
|
||||
assert(result != preexisting_node->env.end());
|
||||
const env_var_t &var = result->second;
|
||||
if (var.exports()) {
|
||||
preexisting_entry_exportv = true;
|
||||
preexisting_flags = result->second.get_flags();
|
||||
if (*preexisting_flags & env_var_t::flag_export) {
|
||||
has_changed_new = true;
|
||||
}
|
||||
}
|
||||
@@ -1129,8 +1117,9 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t var_mode, wcst
|
||||
node = preexisting_node;
|
||||
if ((var_mode & (ENV_EXPORT | ENV_UNEXPORT)) == 0) {
|
||||
// Use existing entry's exportv status.
|
||||
var_mode = //!OCLINT(parameter reassignment)
|
||||
preexisting_entry_exportv ? ENV_EXPORT : 0;
|
||||
if (preexisting_flags && (*preexisting_flags & env_var_t::flag_export)) {
|
||||
var_mode |= ENV_EXPORT;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!get_proc_had_barrier()) {
|
||||
@@ -1158,6 +1147,27 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t var_mode, wcst
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
// Resolve if we should mark ourselves as a path variable or not.
|
||||
// If there's an existing variable, use its path flag; otherwise infer it.
|
||||
if ((var_mode & (ENV_PATHVAR | ENV_UNPATHVAR)) == 0) {
|
||||
bool should_pathvar = false;
|
||||
if (auto existing = node->find_entry(key)) {
|
||||
should_pathvar = existing->is_pathvar();
|
||||
} else {
|
||||
should_pathvar = variable_should_auto_pathvar(key);
|
||||
}
|
||||
var_mode |= should_pathvar ? ENV_PATHVAR : ENV_UNPATHVAR;
|
||||
}
|
||||
|
||||
// Split about ':' if it's a path variable.
|
||||
if (var_mode & ENV_PATHVAR) {
|
||||
wcstring_list_t split_val;
|
||||
for (const wcstring &str : val) {
|
||||
vec_append(split_val, split_string(str, PATH_ARRAY_SEP));
|
||||
}
|
||||
val = std::move(split_val);
|
||||
}
|
||||
|
||||
// Set the entry in the node. Note that operator[] accesses the existing entry, or
|
||||
// creates a new one.
|
||||
env_var_t &var = node->env[key];
|
||||
@@ -1167,6 +1177,7 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t var_mode, wcst
|
||||
}
|
||||
|
||||
var.set_vals(std::move(val));
|
||||
var.set_pathvar(var_mode & ENV_PATHVAR);
|
||||
|
||||
if (var_mode & ENV_EXPORT) {
|
||||
// The new variable is exported.
|
||||
@@ -1291,11 +1302,13 @@ int env_remove(const wcstring &key, int var_mode) {
|
||||
|
||||
const wcstring_list_t &env_var_t::as_list() const { return vals; }
|
||||
|
||||
/// Return a string representation of the var. At the present time this uses the legacy 2.x
|
||||
/// encoding.
|
||||
wchar_t env_var_t::get_delimiter() const {
|
||||
return is_pathvar() ? PATH_ARRAY_SEP : NONPATH_ARRAY_SEP;
|
||||
}
|
||||
|
||||
/// Return a string representation of the var.
|
||||
wcstring env_var_t::as_string() const {
|
||||
wchar_t sep = (flags & flag_colon_delimit) ? L':' : ARRAY_SEP;
|
||||
return join_strings(vals, sep);
|
||||
return join_strings(vals, get_delimiter());
|
||||
}
|
||||
|
||||
void env_var_t::to_list(wcstring_list_t &out) const {
|
||||
@@ -1305,7 +1318,6 @@ void env_var_t::to_list(wcstring_list_t &out) const {
|
||||
env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) {
|
||||
env_var_flags_t result = 0;
|
||||
if (is_read_only(name)) result |= flag_read_only;
|
||||
if (variable_is_colon_delimited_var(name)) result |= flag_colon_delimit;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1496,13 +1508,6 @@ static std::vector<std::string> get_export_list(const var_table_t &envs) {
|
||||
for (const auto &kv : envs) {
|
||||
std::string ks = wcs2string(kv.first);
|
||||
std::string vs = wcs2string(kv.second.as_string());
|
||||
|
||||
// Arrays in the value are ASCII record separator (0x1e) delimited. But some variables
|
||||
// should have colons. Add those.
|
||||
if (variable_is_colon_delimited_var(kv.first)) {
|
||||
// Replace ARRAY_SEP with colon.
|
||||
std::replace(vs.begin(), vs.end(), (char)ARRAY_SEP, ':');
|
||||
}
|
||||
// Create and append a string of the form ks=vs
|
||||
std::string str;
|
||||
str.reserve(ks.size() + 1 + vs.size());
|
||||
|
||||
33
src/env.h
33
src/env.h
@@ -16,10 +16,6 @@
|
||||
extern size_t read_byte_limit;
|
||||
extern bool curses_initialized;
|
||||
|
||||
/// Character for separating two array elements. We use 30, i.e. the ascii record separator since
|
||||
/// that seems logical.
|
||||
#define ARRAY_SEP (wchar_t)0x1e
|
||||
|
||||
// Flags that may be passed as the 'mode' in env_set / env_get.
|
||||
enum {
|
||||
/// Default mode. Used with `env_get()` to indicate the caller doesn't care what scope the var
|
||||
@@ -35,11 +31,15 @@ enum {
|
||||
ENV_EXPORT = 1 << 3,
|
||||
/// Flag for unexported variable.
|
||||
ENV_UNEXPORT = 1 << 4,
|
||||
/// Flag to mark a variable as a path variable.
|
||||
ENV_PATHVAR = 1 << 5,
|
||||
/// Flag to unmark a variable as a path variable.
|
||||
ENV_UNPATHVAR = 1 << 6,
|
||||
/// Flag for variable update request from the user. All variable changes that are made directly
|
||||
/// by the user, such as those from the `read` and `set` builtin must have this flag set. It
|
||||
/// serves one purpose: to indicate that an error should be returned if the user is attempting
|
||||
/// to modify a var that should not be modified by direct user action; e.g., a read-only var.
|
||||
ENV_USER = 1 << 5,
|
||||
ENV_USER = 1 << 7,
|
||||
};
|
||||
typedef uint32_t env_mode_flags_t;
|
||||
|
||||
@@ -63,16 +63,18 @@ void env_init(const struct config_paths_t *paths = NULL);
|
||||
void misc_init();
|
||||
|
||||
class env_var_t {
|
||||
private:
|
||||
public:
|
||||
using env_var_flags_t = uint8_t;
|
||||
|
||||
private:
|
||||
wcstring_list_t vals; // list of values assigned to the var
|
||||
env_var_flags_t flags;
|
||||
|
||||
public:
|
||||
enum {
|
||||
flag_export = 1 << 0, // whether the variable is exported
|
||||
flag_colon_delimit = 1 << 1, // whether the variable is colon delimited
|
||||
flag_read_only = 1 << 2 // whether the variable is read only
|
||||
flag_export = 1 << 0, // whether the variable is exported
|
||||
flag_read_only = 1 << 1, // whether the variable is read only
|
||||
flag_pathvar = 1 << 2, // whether the variable is a path variable
|
||||
};
|
||||
|
||||
// Constructors.
|
||||
@@ -92,11 +94,16 @@ class env_var_t {
|
||||
bool empty() const { return vals.empty() || (vals.size() == 1 && vals[0].empty()); };
|
||||
bool read_only() const { return flags & flag_read_only; }
|
||||
bool exports() const { return flags & flag_export; }
|
||||
bool is_pathvar() const { return flags & flag_pathvar; }
|
||||
env_var_flags_t get_flags() const { return flags; }
|
||||
|
||||
wcstring as_string() const;
|
||||
void to_list(wcstring_list_t &out) const;
|
||||
const wcstring_list_t &as_list() const;
|
||||
|
||||
/// \return the character used when delimiting quoted expansion.
|
||||
wchar_t get_delimiter() const;
|
||||
|
||||
void set_vals(wcstring_list_t v) { vals = std::move(v); }
|
||||
|
||||
void set_exports(bool exportv) {
|
||||
@@ -107,6 +114,14 @@ class env_var_t {
|
||||
}
|
||||
}
|
||||
|
||||
void set_pathvar(bool pathvar) {
|
||||
if (pathvar) {
|
||||
flags |= flag_pathvar;
|
||||
} else {
|
||||
flags &= ~flag_pathvar;
|
||||
}
|
||||
}
|
||||
|
||||
static env_var_flags_t flags_for(const wchar_t *name);
|
||||
|
||||
env_var_t &operator=(const env_var_t &var) = default;
|
||||
|
||||
@@ -199,16 +199,20 @@ static bool append_file_entry(fish_message_type_t type, const wcstring &key_in,
|
||||
/// Encoding of a null string.
|
||||
static const wchar_t * const ENV_NULL = L"\x1d";
|
||||
|
||||
/// Character used to separate arrays in universal variables file.
|
||||
/// This is 30, the ASCII record separator.
|
||||
static const wchar_t UVAR_ARRAY_SEP = 0x1e;
|
||||
|
||||
/// Decode a serialized universal variable value into a list.
|
||||
static wcstring_list_t decode_serialized(const wcstring &val) {
|
||||
if (val == ENV_NULL) return {};
|
||||
return split_string(val, ARRAY_SEP);
|
||||
return split_string(val, UVAR_ARRAY_SEP);
|
||||
}
|
||||
|
||||
/// Decode a a list into a serialized universal variable value.
|
||||
static wcstring encode_serialized(const wcstring_list_t &vals) {
|
||||
if (vals.empty()) return ENV_NULL;
|
||||
return join_strings(vals, ARRAY_SEP);
|
||||
return join_strings(vals, UVAR_ARRAY_SEP);
|
||||
}
|
||||
|
||||
env_universal_t::env_universal_t(wcstring path) : explicit_vars_path(std::move(path)) {}
|
||||
|
||||
@@ -447,6 +447,9 @@ static bool expand_variables(wcstring instr, std::vector<completion_t> *out, siz
|
||||
}
|
||||
|
||||
if (is_single) {
|
||||
// Quoted expansion. Here we expect the variable's delimiter.
|
||||
// Note history always has a space delimiter.
|
||||
wchar_t delimit = history ? L' ' : var->get_delimiter();
|
||||
wcstring res(instr, 0, varexp_char_idx);
|
||||
if (!res.empty()) {
|
||||
if (res.back() != VARIABLE_EXPAND_SINGLE) {
|
||||
@@ -457,15 +460,8 @@ static bool expand_variables(wcstring instr, std::vector<completion_t> *out, siz
|
||||
}
|
||||
}
|
||||
|
||||
// Append all entries in var_item_list, separated by spaces.
|
||||
// Remove the last space.
|
||||
if (!var_item_list.empty()) {
|
||||
for (const wcstring &item : var_item_list) {
|
||||
res.append(item);
|
||||
res.push_back(L' ');
|
||||
}
|
||||
res.pop_back();
|
||||
}
|
||||
// Append all entries in var_item_list, separated by the delimiter.
|
||||
res.append(join_strings(var_item_list, delimit));
|
||||
res.append(instr, var_name_and_slice_stop, wcstring::npos);
|
||||
return expand_variables(std::move(res), out, varexp_char_idx, errors);
|
||||
} else {
|
||||
|
||||
@@ -19,3 +19,6 @@ echo $$paren
|
||||
|
||||
####################
|
||||
# Test tilde expansion
|
||||
|
||||
####################
|
||||
# Test path variables
|
||||
|
||||
@@ -126,3 +126,18 @@ end
|
||||
unlink $tmpdir/linkhome
|
||||
rmdir $tmpdir/realhome
|
||||
rmdir $tmpdir
|
||||
|
||||
logmsg Test path variables
|
||||
set TEST_DELIMITER one two three
|
||||
set TEST_DELIMITER_PATH one two three
|
||||
echo TEST_DELIMITER: $TEST_DELIMITER "$TEST_DELIMITER"
|
||||
echo TEST_DELIMITER_PATH: $TEST_DELIMITER_PATH "$TEST_DELIMITER_PATH"
|
||||
|
||||
set testvar ONE:TWO:THREE
|
||||
echo "Not a path: $testvar" (count $testvar)
|
||||
set --path testvar $testvar
|
||||
echo "As a path: $testvar" (count $testvar)
|
||||
set testvar "$testvar:FOUR"
|
||||
echo "Appended path: $testvar" (count $testvar)
|
||||
set --unpath testvar $testvar
|
||||
echo "Back to normal variable: $testvar" (count $testvar)
|
||||
|
||||
@@ -73,3 +73,12 @@ All digits: 0
|
||||
|
||||
####################
|
||||
# Test tilde expansion
|
||||
|
||||
####################
|
||||
# Test path variables
|
||||
TEST_DELIMITER: one two three one two three
|
||||
TEST_DELIMITER_PATH: one two three one:two:three
|
||||
Not a path: ONE:TWO:THREE 1
|
||||
As a path: ONE:TWO:THREE 3
|
||||
Appended path: ONE:TWO:THREE:FOUR 4
|
||||
Back to normal variable: ONE TWO THREE FOUR 4
|
||||
|
||||
@@ -105,7 +105,7 @@ $var6: not set in universal scope
|
||||
# Exporting works
|
||||
TESTVAR0=
|
||||
TESTVAR1=a
|
||||
TESTVAR2=a^^b
|
||||
TESTVAR2=a b
|
||||
|
||||
####################
|
||||
# if/for/while scope
|
||||
|
||||
@@ -300,10 +300,15 @@ env SHLVL=" 3" ../test/root/bin/fish -c 'echo SHLVL: $SHLVL'
|
||||
env DISPLAY="localhost:0.0" ../test/root/bin/fish -c 'echo Elements in DISPLAY: (count $DISPLAY)'
|
||||
# We can't use PATH for this because the global configuration will modify PATH
|
||||
# based on /etc/paths and /etc/paths.d.
|
||||
# Exported arrays should use record separator, with a few exceptions. So we can use an arbitrary variable for this.
|
||||
# Exported arrays are colon delimited; they are automatically split on colons if they end in PATH.
|
||||
set -gx FOO one two three four
|
||||
../test/root/bin/fish -c 'echo Elements in FOO: (count $FOO)'
|
||||
set -gx FOOPATH one two three four
|
||||
../test/root/bin/fish -c 'echo Elements in FOO and FOOPATH: (count $FOO) (count $FOOPATH)'
|
||||
# some must use colon separators!
|
||||
set -lx MANPATH man1 man2 man3 ; env | grep '^MANPATH='
|
||||
# ensure we don't escape space and colon values
|
||||
set -x DONT_ESCAPE_COLONS 1: 2: :3: ; env | grep '^DONT_ESCAPE_COLONS='
|
||||
set -x DONT_ESCAPE_SPACES '1 ' '2 ' ' 3 ' 4 ; env | grep '^DONT_ESCAPE_SPACES='
|
||||
set -x DONT_ESCAPE_COLONS_PATH 1: 2: :3: ; env | grep '^DONT_ESCAPE_COLONS_PATH='
|
||||
|
||||
true
|
||||
|
||||
@@ -41,5 +41,8 @@ SHLVL: 1
|
||||
SHLVL: 4
|
||||
SHLVL: 4
|
||||
Elements in DISPLAY: 1
|
||||
Elements in FOO: 4
|
||||
Elements in FOO and FOOPATH: 1 4
|
||||
MANPATH=man1:man2:man3
|
||||
DONT_ESCAPE_COLONS=1: 2: :3:
|
||||
DONT_ESCAPE_SPACES=1 2 3 4
|
||||
DONT_ESCAPE_COLONS_PATH=1::2:::3:
|
||||
|
||||
Reference in New Issue
Block a user