mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 12:23:09 +00:00
Remove caret redirection
This removes the caret as a shorthand for redirecting stderr. Note that stderr may be redirected to a file via 2>/some/path... and may be redirected with a pipe via 2>|. Fixes #4394
This commit is contained in:
parent
7659554dea
commit
5b489ca30f
11 changed files with 28 additions and 47 deletions
|
@ -15,6 +15,8 @@ This section is for changes merged to the `major` branch that are not also merge
|
||||||
- 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).
|
- 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).
|
||||||
- `%` is no longer used for process and job expansion. `$fish_pid` and `$last_pid` have taken the place of `%self` and `%last` respectively. (#4230, #1202)
|
- `%` is no longer used for process and job expansion. `$fish_pid` and `$last_pid` have taken the place of `%self` and `%last` respectively. (#4230, #1202)
|
||||||
- The new `math` builtin (see below) does not support logical expressions; `test` should be used instead (#4777).
|
- The new `math` builtin (see below) does not support logical expressions; `test` should be used instead (#4777).
|
||||||
|
- The `?` wildcard has been removed (#4520).
|
||||||
|
- The `^` caret redirection for stderr has been removed (#4394). To redirect stderr, `2>/some/path` may be used, or `2>|` as a pipe.
|
||||||
|
|
||||||
## Notable fixes and improvements
|
## Notable fixes and improvements
|
||||||
- `wait` builtin is added for waiting on processes (#4498).
|
- `wait` builtin is added for waiting on processes (#4498).
|
||||||
|
@ -51,7 +53,6 @@ This section is for changes merged to the `major` branch that are not also merge
|
||||||
- The machine hostname, where available, is now exposed as `$hostname` which is now a reserved variable. This drops the dependency on the `hostname` executable (#4422).
|
- The machine hostname, where available, is now exposed as `$hostname` which is now a reserved variable. This drops the dependency on the `hostname` executable (#4422).
|
||||||
- `functions --handlers` can be used to show event handlers (#4694).
|
- `functions --handlers` can be used to show event handlers (#4694).
|
||||||
- Variables set in `if` and `while` conditions are available outside the block (#4820).
|
- Variables set in `if` and `while` conditions are available outside the block (#4820).
|
||||||
- The `?` wildcard has been removed (#4520).
|
|
||||||
|
|
||||||
## Other significant changes
|
## Other significant changes
|
||||||
- Command substitution output is now limited to 10 MB by default (#3822).
|
- Command substitution output is now limited to 10 MB by default (#3822).
|
||||||
|
|
|
@ -1007,7 +1007,6 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
|
||||||
case L'$':
|
case L'$':
|
||||||
case L' ':
|
case L' ':
|
||||||
case L'#':
|
case L'#':
|
||||||
case L'^':
|
|
||||||
case L'<':
|
case L'<':
|
||||||
case L'>':
|
case L'>':
|
||||||
case L'(':
|
case L'(':
|
||||||
|
|
|
@ -534,7 +534,7 @@ static void test_tokenizer() {
|
||||||
|
|
||||||
const wchar_t *str =
|
const wchar_t *str =
|
||||||
L"string <redirection 2>&1 'nested \"quoted\" '(string containing subshells "
|
L"string <redirection 2>&1 'nested \"quoted\" '(string containing subshells "
|
||||||
L"){and,brackets}$as[$well (as variable arrays)] not_a_redirect^ ^ ^^is_a_redirect "
|
L"){and,brackets}$as[$well (as variable arrays)] not_a_redirect^ 2> 2>^is_a_redirect "
|
||||||
L"&&& ||| "
|
L"&&& ||| "
|
||||||
L"&& || & |"
|
L"&& || & |"
|
||||||
L"Compress_Newlines\n \n\t\n \nInto_Just_One";
|
L"Compress_Newlines\n \n\t\n \nInto_Just_One";
|
||||||
|
@ -608,8 +608,6 @@ static void test_tokenizer() {
|
||||||
// Test redirection_type_for_string.
|
// Test redirection_type_for_string.
|
||||||
if (redirection_type_for_string(L"<") != redirection_type_t::input)
|
if (redirection_type_for_string(L"<") != redirection_type_t::input)
|
||||||
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
||||||
if (redirection_type_for_string(L"^") != redirection_type_t::overwrite)
|
|
||||||
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
|
||||||
if (redirection_type_for_string(L">") != redirection_type_t::overwrite)
|
if (redirection_type_for_string(L">") != redirection_type_t::overwrite)
|
||||||
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
err(L"redirection_type_for_string failed on line %ld", (long)__LINE__);
|
||||||
if (redirection_type_for_string(L"2>") != redirection_type_t::overwrite)
|
if (redirection_type_for_string(L"2>") != redirection_type_t::overwrite)
|
||||||
|
|
|
@ -70,10 +70,8 @@ bool tokenizer_t::next(struct tok_t *result) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests if this character can be a part of a string. The redirect ^ is allowed unless it's the
|
/// Tests if this character can be a part of a string.
|
||||||
/// first character. Hash (#) starts a comment if it's the first character in a token; otherwise it
|
static bool tok_is_string_character(wchar_t c) {
|
||||||
/// is considered a string character. See issue #953.
|
|
||||||
static bool tok_is_string_character(wchar_t c, bool is_first) {
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case L'\0':
|
case L'\0':
|
||||||
case L' ':
|
case L' ':
|
||||||
|
@ -84,15 +82,9 @@ static bool tok_is_string_character(wchar_t c, bool is_first) {
|
||||||
case L'\r':
|
case L'\r':
|
||||||
case L'<':
|
case L'<':
|
||||||
case L'>':
|
case L'>':
|
||||||
case L'&': {
|
case L'&':
|
||||||
// Unconditional separators.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
default: return true;
|
||||||
case L'^': {
|
|
||||||
// Conditional separator.
|
|
||||||
return !is_first;
|
|
||||||
}
|
|
||||||
default: { return true; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +109,6 @@ tok_t tokenizer_t::read_string() {
|
||||||
std::vector<char> expecting;
|
std::vector<char> expecting;
|
||||||
int slice_offset = 0;
|
int slice_offset = 0;
|
||||||
const wchar_t *const buff_start = this->buff;
|
const wchar_t *const buff_start = this->buff;
|
||||||
bool is_first = true;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
wchar_t c = *this->buff;
|
wchar_t c = *this->buff;
|
||||||
|
@ -219,7 +210,7 @@ tok_t tokenizer_t::read_string() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode == tok_mode::regular_text && !tok_is_string_character(c, is_first)) {
|
else if (mode == tok_mode::regular_text && !tok_is_string_character(c)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +224,6 @@ tok_t tokenizer_t::read_string() {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
this->buff++;
|
this->buff++;
|
||||||
is_first = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!this->accept_unfinished) && (mode != tok_mode::regular_text)) {
|
if ((!this->accept_unfinished) && (mode != tok_mode::regular_text)) {
|
||||||
|
@ -292,7 +282,7 @@ static maybe_t<parsed_redir_or_pipe_t> read_redirection_or_fd_pipe(const wchar_t
|
||||||
size_t idx = 0;
|
size_t idx = 0;
|
||||||
|
|
||||||
// Determine the fd. This may be specified as a prefix like '2>...' or it may be implicit like
|
// Determine the fd. This may be specified as a prefix like '2>...' or it may be implicit like
|
||||||
// '>' or '^'. Try parsing out a number; if we did not get any digits then infer it from the
|
// '>'. Try parsing out a number; if we did not get any digits then infer it from the
|
||||||
// first character. Watch out for overflow.
|
// first character. Watch out for overflow.
|
||||||
long long big_fd = 0;
|
long long big_fd = 0;
|
||||||
for (; iswdigit(buff[idx]); idx++) {
|
for (; iswdigit(buff[idx]); idx++) {
|
||||||
|
@ -313,10 +303,6 @@ static maybe_t<parsed_redir_or_pipe_t> read_redirection_or_fd_pipe(const wchar_t
|
||||||
result.fd = STDIN_FILENO;
|
result.fd = STDIN_FILENO;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case L'^': {
|
|
||||||
result.fd = STDERR_FILENO;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
errored = true;
|
errored = true;
|
||||||
break;
|
break;
|
||||||
|
@ -325,12 +311,11 @@ static maybe_t<parsed_redir_or_pipe_t> read_redirection_or_fd_pipe(const wchar_t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either way we should have ended on the redirection character itself like '>'.
|
// Either way we should have ended on the redirection character itself like '>'.
|
||||||
// Don't allow an fd with a caret redirection - see #1873
|
|
||||||
wchar_t redirect_char = buff[idx++]; // note increment of idx
|
wchar_t redirect_char = buff[idx++]; // note increment of idx
|
||||||
if (redirect_char == L'>' || (redirect_char == L'^' && idx == 1)) {
|
if (redirect_char == L'>') {
|
||||||
result.redirection_mode = redirection_type_t::overwrite;
|
result.redirection_mode = redirection_type_t::overwrite;
|
||||||
if (buff[idx] == redirect_char) {
|
if (buff[idx] == redirect_char) {
|
||||||
// Doubled up like ^^ or >>. That means append.
|
// Doubled up like >>. That means append.
|
||||||
result.redirection_mode = redirection_type_t::append;
|
result.redirection_mode = redirection_type_t::append;
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
|
@ -505,8 +490,7 @@ maybe_t<tok_t> tokenizer_t::tok_next() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case L'>':
|
case L'>':
|
||||||
case L'<':
|
case L'<': {
|
||||||
case L'^': {
|
|
||||||
// There's some duplication with the code in the default case below. The key difference
|
// There's some duplication with the code in the default case below. The key difference
|
||||||
// here is that we must never parse these as a string; a failed redirection is an error!
|
// here is that we must never parse these as a string; a failed redirection is an error!
|
||||||
auto redir_or_pipe = read_redirection_or_fd_pipe(this->buff);
|
auto redir_or_pipe = read_redirection_or_fd_pipe(this->buff);
|
||||||
|
@ -615,9 +599,7 @@ bool move_word_state_machine_t::consume_char_punctuation(wchar_t c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool move_word_state_machine_t::is_path_component_character(wchar_t c) {
|
bool move_word_state_machine_t::is_path_component_character(wchar_t c) {
|
||||||
// Always treat separators as first. All this does is ensure that we treat ^ as a string
|
return tok_is_string_character(c) && !wcschr(L"/={,}'\"", c);
|
||||||
// character instead of as stderr redirection, which I hypothesize is usually what is desired.
|
|
||||||
return tok_is_string_character(c, true) && !wcschr(L"/={,}'\"", c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool move_word_state_machine_t::consume_char_path_components(wchar_t c) {
|
bool move_word_state_machine_t::consume_char_path_components(wchar_t c) {
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
for i in $argv
|
for i in $argv
|
||||||
set template_out (basename $i .in).out
|
set template_out (basename $i .in).out
|
||||||
set template_err (basename $i .in).err
|
set template_err (basename $i .in).err
|
||||||
set template_status (basename $i .in).status
|
set template_status (basename $i .in).status
|
||||||
|
|
||||||
fish <$i >$template_out ^$template_err
|
fish <$i >$template_out 2>$template_err
|
||||||
echo $status >$template_status
|
echo $status >$template_status
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,8 +32,9 @@ else
|
||||||
set files_to_test *.expect
|
set files_to_test *.expect
|
||||||
end
|
end
|
||||||
|
|
||||||
source test_util.fish (status -f) $argv; or exit
|
source test_util.fish (status -f) $argv
|
||||||
cat interactive.config >> $XDG_CONFIG_HOME/fish/config.fish
|
or exit
|
||||||
|
cat interactive.config >>$XDG_CONFIG_HOME/fish/config.fish
|
||||||
|
|
||||||
say -o cyan "Testing interactive functionality"
|
say -o cyan "Testing interactive functionality"
|
||||||
if not type -q expect
|
if not type -q expect
|
||||||
|
@ -46,7 +47,7 @@ function test_file
|
||||||
echo -n "Testing file $file ... "
|
echo -n "Testing file $file ... "
|
||||||
begin
|
begin
|
||||||
set -lx TERM dumb
|
set -lx TERM dumb
|
||||||
expect -n -c 'source interactive.expect.rc' -f $file >$file.tmp.out ^$file.tmp.err
|
expect -n -c 'source interactive.expect.rc' -f $file >$file.tmp.out 2>$file.tmp.err
|
||||||
end
|
end
|
||||||
set -l exit_status $status
|
set -l exit_status $status
|
||||||
set -l res ok
|
set -l res ok
|
||||||
|
|
|
@ -3,7 +3,7 @@ sleep 5 &
|
||||||
sleep 5 &
|
sleep 5 &
|
||||||
jobs -c
|
jobs -c
|
||||||
jobs -q; echo $status
|
jobs -q; echo $status
|
||||||
bg -23 1 ^/dev/null
|
bg -23 1 2>/dev/null
|
||||||
or echo bg: invalid option -23 >&2
|
or echo bg: invalid option -23 >&2
|
||||||
fg 3
|
fg 3
|
||||||
bg 3
|
bg 3
|
||||||
|
|
|
@ -28,7 +28,7 @@ printf 'a\cb'
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Bogus printf specifier, should produce no stdout
|
# Bogus printf specifier, should produce no stdout
|
||||||
printf "%5" 10 ^/dev/null
|
printf "%5" 10 2>/dev/null
|
||||||
|
|
||||||
# Octal escapes produce literal bytes, not characters
|
# Octal escapes produce literal bytes, not characters
|
||||||
# \376 is 0xFE
|
# \376 is 0xFE
|
||||||
|
|
|
@ -141,7 +141,7 @@ set line abcdefghijklmnopqrstuvwxyz
|
||||||
# Ensure the `read` command terminates if asked to read too much data. The var
|
# Ensure the `read` command terminates if asked to read too much data. The var
|
||||||
# should be empty. We throw away any data we read if it exceeds the limit on
|
# should be empty. We throw away any data we read if it exceeds the limit on
|
||||||
# what we consider reasonable.
|
# what we consider reasonable.
|
||||||
yes $line | dd bs=1024 count=(math "1 + $fish_read_limit / 1024") ^/dev/null | read --null x
|
yes $line | dd bs=1024 count=(math "1 + $fish_read_limit / 1024") 2>/dev/null | read --null x
|
||||||
if test $status -ne 122
|
if test $status -ne 122
|
||||||
echo reading too much data did not terminate with failure status
|
echo reading too much data did not terminate with failure status
|
||||||
end
|
end
|
||||||
|
@ -188,7 +188,7 @@ end
|
||||||
|
|
||||||
# Same as previous test but limit the amount of data fed to `read` rather than
|
# Same as previous test but limit the amount of data fed to `read` rather than
|
||||||
# using the `--nchars` flag.
|
# using the `--nchars` flag.
|
||||||
yes $line | dd bs=1024 count=(math "$fish_read_limit / 1024") ^/dev/null | read --null x
|
yes $line | dd bs=1024 count=(math "$fish_read_limit / 1024") 2>/dev/null | read --null x
|
||||||
if test $status -ne 0
|
if test $status -ne 0
|
||||||
echo the read of the max amount of data failed unexpectedly
|
echo the read of the max amount of data failed unexpectedly
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,7 +35,7 @@ function test_file
|
||||||
|
|
||||||
echo -n "Testing file $file ... "
|
echo -n "Testing file $file ... "
|
||||||
|
|
||||||
../test/root/bin/fish <$file >$base.tmp.out ^$base.tmp.err
|
../test/root/bin/fish <$file >$base.tmp.out 2>$base.tmp.err
|
||||||
set -l exit_status $status
|
set -l exit_status $status
|
||||||
set -l res ok
|
set -l res ok
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ if begin; rm -rf test6.tmp.dir; and mkdir test6.tmp.dir; end
|
||||||
end
|
end
|
||||||
popd
|
popd
|
||||||
if begin
|
if begin
|
||||||
set -l PATH $PWD/test6.tmp.dir $PATH ^/dev/null
|
set -l PATH $PWD/test6.tmp.dir $PATH 2>/dev/null
|
||||||
complete -C$dir | grep "^$dir/.*Directory" >/dev/null
|
complete -C$dir | grep "^$dir/.*Directory" >/dev/null
|
||||||
end
|
end
|
||||||
echo "incorrect implicit cd from PATH"
|
echo "incorrect implicit cd from PATH"
|
||||||
|
|
Loading…
Reference in a new issue