Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Brace expansion now only takes place if the braces include a "," or a variable expansion, meaning common commands such as `git reset HEAD@{0}` do not require escaping (#5869).
- New redirections `&>` and `&|` may be used to redirect or pipe stdout, and also redirect stderr to stdout (#6192).
- `switch` now allows arguments that expand to nothing, like empty variables (#5677).
- The `VAR=val cmd` syntax can now be used to run a command in a modified environment (#6287).

### Scripting improvements
- `string split0` now returns 0 if it split something (#5701).
Expand Down
6 changes: 6 additions & 0 deletions sphinx_doc_src/cmds/set.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ Examples
echo "Python is at $python_path"
end

# Like other shells, fish 3.1 supports this syntax for passing a variable to just one command:
# Run fish with a temporary home directory.
HOME=(mktemp -d) fish
# Which is essentially the same as:
begin; set -lx HOME (mktemp -d); fish; end

Notes
-----

Expand Down
20 changes: 4 additions & 16 deletions sphinx_doc_src/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Use the :ref:`set <cmd-set>` command::
set -x key value
set -e key

Since fish 3.1 you can set an environment variable for just one command using the ``key=value some command`` syntax, like in other shells. The two lines below behave identically - unlike other shells, fish will output ``value`` both times::

key=value echo $key
begin; set -lx key value; echo $key; end

How do I run a command every login? What's fish's equivalent to .bashrc?
------------------------------------------------------------------------
Expand Down Expand Up @@ -95,22 +99,6 @@ If you are just interested in success or failure, you can run the command direct

See the documentation for :ref:`test <cmd-test>` and :ref:`if <cmd-if>` for more information.

How do I set an environment variable for just one command?
----------------------------------------------------------
``SOME_VAR=1 command`` produces an error: ``Unknown command "SOME_VAR=1"``.

Use the ``env`` command.

``env SOME_VAR=1 command``

You can also declare a local variable in a block::

begin
set -lx SOME_VAR 1
command
end


How do I check whether a variable is defined?
---------------------------------------------

Expand Down
32 changes: 29 additions & 3 deletions src/complete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,11 @@ void completer_t::perform() {
}

if (cmd_tok.location_in_or_at_end_of_source_range(cursor_pos)) {
maybe_t<size_t> equal_sign_pos = variable_assignment_equals_pos(current_token);
if (equal_sign_pos) {
complete_param_expand(current_token.substr(*equal_sign_pos + 1), true /* do_file */);
return;
}
// Complete command filename.
complete_cmd(current_token);
complete_abbr(current_token);
Expand Down Expand Up @@ -1570,9 +1575,30 @@ void completer_t::perform() {
if (wants_transient) {
parser->libdata().transient_commandlines.push_back(cmdline);
}
// Now invoke any custom completions for this command.
if (!complete_param(cmd, previous_argument_unescape, current_argument_unescape,
!had_ddash)) {
bool is_variable_assignment = bool(variable_assignment_equals_pos(cmd));
if (is_variable_assignment && parser) {
// To avoid issues like #2705 we complete commands starting with variable
// assignments by recursively calling complete for the command suffix
// without the first variable assignment token.
wcstring unaliased_cmd;
if (parser->libdata().transient_commandlines.empty()) {
unaliased_cmd = cmdline;
} else {
unaliased_cmd = parser->libdata().transient_commandlines.back();
}
tokenizer_t tok(unaliased_cmd.c_str(), TOK_ACCEPT_UNFINISHED);
maybe_t<tok_t> cmd_tok = tok.next();
assert(cmd_tok);
unaliased_cmd = unaliased_cmd.replace(0, cmd_tok->offset + cmd_tok->length, L"");
parser->libdata().transient_commandlines.push_back(unaliased_cmd);
cleanup_t remove_transient([&] { parser->libdata().transient_commandlines.pop_back(); });
std::vector<completion_t> comp;
complete(unaliased_cmd, &comp,
completion_request_t::fuzzy_match, parser->vars(), parser->shared());
this->completions.insert(completions.end(), comp.begin(), comp.end());
do_file = false;
} else if (!complete_param(cmd, previous_argument_unescape, current_argument_unescape,
!had_ddash)) { // Invoke any custom completions for this command.
do_file = false;
}
if (wants_transient) {
Expand Down
11 changes: 11 additions & 0 deletions src/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,17 @@ static bool exec_process_in_job(parser_t &parser, process_t *p, std::shared_ptr<
parser.libdata().exec_count++;
}

const block_t *block = nullptr;
cleanup_t pop_block([&]() {
if (block) parser.pop_block(block);
});
if (!p->variable_assignments.empty()) {
block = parser.push_block(block_t::variable_assignment_block());
}
for (const auto &assignment : p->variable_assignments) {
parser.vars().set(assignment.variable_name, ENV_LOCAL | ENV_EXPORT, assignment.values);
}

// Execute the process.
p->check_generations_before_launch();
switch (p->type) {
Expand Down
1 change: 1 addition & 0 deletions src/fish_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4626,6 +4626,7 @@ static void test_highlighting() {
});

highlight_tests.push_back({
{L"HOME=.", highlight_role_t::param},
{L"false", highlight_role_t::command},
{L"|&", highlight_role_t::error},
{L"true", highlight_role_t::command},
Expand Down
8 changes: 8 additions & 0 deletions src/highlight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,12 @@ highlighter_t::color_array_t highlighter_t::highlight() {
this->color_node(node, highlight_role_t::operat);
break;

case symbol_variable_assignment: {
tnode_t<g::variable_assignment> variable_assignment = {&parse_tree, &node};
this->color_argument(variable_assignment.child<0>());
break;
}

case parse_token_type_pipe:
case parse_token_type_background:
case parse_token_type_end:
Expand All @@ -1223,6 +1229,8 @@ highlighter_t::color_array_t highlighter_t::highlight() {
if (!this->io_ok) {
// We cannot check if the command is invalid, so just assume it's valid.
is_valid_cmd = true;
} else if (variable_assignment_equals_pos(*cmd)) {
is_valid_cmd = true;
} else {
wcstring expanded_cmd;
// Check to see if the command is valid.
Expand Down
7 changes: 2 additions & 5 deletions src/parse_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ enum parse_token_type_t : uint8_t {
symbol_not_statement,
symbol_decorated_statement,
symbol_plain_statement,
symbol_variable_assignment,
symbol_variable_assignments,
symbol_arguments_or_redirections_list,
symbol_andor_job_list,
symbol_argument_list,
Expand Down Expand Up @@ -277,11 +279,6 @@ void parse_error_offset_source_start(parse_error_list_t *errors, size_t amt);
/// Error issued on $.
#define ERROR_NO_VAR_NAME _(L"Expected a variable name after this $.")

/// Error on foo=bar.
#define ERROR_BAD_EQUALS_IN_COMMAND5 \
_(L"Unsupported use of '='. To run '%ls' with a modified environment, please use 'env " \
L"%ls=%ls %ls%ls'")

/// Error message for Posix-style assignment: foo=bar.
#define ERROR_BAD_COMMAND_ASSIGN_ERR_MSG \
_(L"Unsupported use of '='. In fish, please use 'set %ls %ls'.")
Expand Down
Loading