Accept |& as an alias for &|

Closes #12565
This commit is contained in:
Rodolfo Gatti
2026-03-23 13:05:48 -03:00
committed by Johannes Altmanninger
parent d36c53c6e2
commit 9ea760c401
15 changed files with 11 additions and 46 deletions

View File

@@ -23,6 +23,7 @@ Improved terminal support
Other improvements
------------------
- For compatibility with Bash, fish now accepts ``|&`` as alternate spelling of ``&|``, for piping pipe both standard output and standard error (:issue:`11516`).
- ``fish_indent`` now preserves comments and newlines immediately preceding a brace block (``{ }``) (:issue:`12505`).
For distributors and developers

View File

@@ -235,7 +235,7 @@ It is possible to pipe a different output file descriptor by prepending its FD n
will attempt to build ``fish``, and any errors will be shown using the ``less`` pager. [#]_
As a convenience, the pipe ``&|`` redirects both stdout and stderr to the same process. This is different from bash, which uses ``|&``.
As a convenience, the pipe ``&|`` (as well as the ``|&`` alias which is also supported by Bash) both redirect stdout and stderr to the same process.
.. [#] A "pager" here is a program that takes output and "paginates" it. ``less`` doesn't just do pages, it allows arbitrary scrolling (even back!).

View File

@@ -1875,9 +1875,6 @@ msgstr "mit den Parametern '%s'"
msgid "with definition"
msgstr "mit Definition"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr "|& ist ungültig. In fish, nutze &| um stdout und stderr gleichzeitig zu pipen"
#, c-format
msgid "…and %u more rows"
msgstr "…und %u weitere Zeilen"

View File

@@ -1875,9 +1875,6 @@ msgstr ""
msgid "with definition"
msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
#, c-format
msgid "…and %u more rows"
msgstr "…and %u more rows"

View File

@@ -1878,9 +1878,6 @@ msgstr "con los argumentos '%s'"
msgid "with definition"
msgstr "con definición"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr "|& no es válido. En fish, usa &| para redirigir stdout y stderr."
#, c-format
msgid "…and %u more rows"
msgstr "…y %u filas más"

View File

@@ -2004,9 +2004,6 @@ msgstr "avec les arguments « %s »"
msgid "with definition"
msgstr "avec la définition"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
#, c-format
msgid "…and %u more rows"
msgstr "…et %u lignes de plus"

View File

@@ -1878,9 +1878,6 @@ msgid "with definition"
msgstr "定義されています"
# CHK
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr "|& は無効です。fish では、標準出力と標準エラー出力の両方をパイプするには &| を使用してください"
#, c-format
msgid "…and %u more rows"
msgstr "…他 %u 行"

View File

@@ -1871,9 +1871,6 @@ msgstr ""
msgid "with definition"
msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
#, fuzzy, c-format
msgid "…and %u more rows"
msgstr "…and %u więcej rzędów"

View File

@@ -1876,9 +1876,6 @@ msgstr ""
msgid "with definition"
msgstr "com definição"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
#, fuzzy, c-format
msgid "…and %u more rows"
msgstr "…e mais %u linhas"

View File

@@ -1872,9 +1872,6 @@ msgstr ""
msgid "with definition"
msgstr ""
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr ""
#, fuzzy, c-format
msgid "…and %u more rows"
msgstr "…och %u rader till"

View File

@@ -1899,9 +1899,6 @@ msgstr ",参数为 '%s'"
msgid "with definition"
msgstr ",定义为"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr "|& 无效。在 fish 中,用 &| 来同时管道链接 stdout 和 stderr。"
#, fuzzy, c-format
msgid "…and %u more rows"
msgstr "…还有 %u 行"

View File

@@ -1873,9 +1873,6 @@ msgstr ",引數為「%s」"
msgid "with definition"
msgstr ",定義為"
msgid "|& is not valid. In fish, use &| to pipe both stdout and stderr."
msgstr "|& 無效。在 fish 中請使用 &| 來同時管道傳輸 stdout 和 stderr。"
# 第一個 %s 是刪節號。此字串出現在按下 <Tab> 的清單太長的情況。
#, fuzzy, c-format
msgid "…and %u more rows"

View File

@@ -1733,7 +1733,7 @@ macro_rules! validate {
("VAR", fg(HighlightRole::param)),
("=", fg(HighlightRole::operat), ns),
("false", fg(HighlightRole::command)),
("|&", fg(HighlightRole::error)),
("|&", fg(HighlightRole::statement_terminator)),
("true", fg(HighlightRole::command)),
("stuff", fg(HighlightRole::param)),
);

View File

@@ -49,7 +49,6 @@ pub enum TokenizerError {
UnterminatedEscape,
InvalidRedirect,
InvalidPipe,
InvalidPipeAmpersand,
ClosingUnopenedSubshell,
IllegalSlice,
ClosingUnopenedBrace,
@@ -95,7 +94,7 @@ pub struct PipeOrRedir {
pub mode: RedirectionMode,
// Whether, in addition to this redirection, stderr should also be dup'd to stdout
// For example &| or &>
// For example &|, |& or &>
pub stderr_merge: bool,
// Number of characters consumed when parsing the string.
@@ -163,9 +162,6 @@ fn from(err: TokenizerError) -> Self {
TokenizerError::InvalidPipe => {
wgettext!("Cannot use stdin (fd 0) as pipe output")
}
TokenizerError::InvalidPipeAmpersand => {
wgettext!("|& is not valid. In fish, use &| to pipe both stdout and stderr.")
}
TokenizerError::ClosingUnopenedSubshell => {
wgettext!("Unexpected ')' for unopened parenthesis")
}
@@ -473,10 +469,6 @@ fn next(&mut self) -> Option<Self::Item> {
self.token_cursor += 2;
at_cmd_pos = true;
Some(result)
} else if next_char == Some('&') {
// |& is a bashism; in fish it's &|.
Some(self.call_error(TokenizerError::InvalidPipeAmpersand,
self.token_cursor, self.token_cursor, Some(2), 2))
} else {
let pipe = PipeOrRedir::try_from(buff).
expect("Should always succeed to parse a | pipe");
@@ -1024,6 +1016,7 @@ fn try_from(buff: &wstr) -> Result<PipeOrRedir, ()> {
);
result.fd = STDOUT_FILENO;
result.is_pipe = true;
result.stderr_merge = try_consume(&mut cursor, '&');
}
'>' => {
consume(&mut cursor, '>');
@@ -1356,6 +1349,9 @@ macro_rules! pipe_or_redir {
assert!(pipe_or_redir!("&|").is_pipe);
assert!(pipe_or_redir!("&|").stderr_merge);
assert!(pipe_or_redir!("|&").is_pipe);
assert!(pipe_or_redir!("|&").stderr_merge);
assert_eq!(pipe_or_redir!("|&").fd, STDOUT_FILENO);
assert!(!pipe_or_redir!("&>").is_pipe);
assert!(pipe_or_redir!("&>").stderr_merge);
assert!(pipe_or_redir!("&>>").stderr_merge);

View File

@@ -8,6 +8,9 @@ end
outnerr 0 &| count
#CHECK: 2
outnerr 1 |& count
#CHECK: 2
outnerr appendfd 2>>&1
#CHECK: out appendfd
@@ -29,11 +32,6 @@ cat $tmpdir/file.txt
echo noclobber &>>?$tmpdir/file.txt
#CHECKERR: {{.*}} The file {{.*}} already exists
eval "echo foo |& false"
#CHECKERR: {{.*}} |& is not valid. In fish, use &| to pipe both stdout and stderr.
#CHECKERR: echo foo |& false
#CHECKERR: ^^
# Ensure that redirection empty data still creates the file.
rm -f $tmpdir/file.txt
test -f $tmpdir/file.txt && echo "File exists" || echo "File does not exist"