我希望它bash
带有一个选项,可以阻止命令替换剥离尾随换行符。有没有?如果没有,那么是否存在任何类似于 POSIX 的 shell bash
,以及运行标准(POSIX,我猜)sh
代码的 shell,它具有这样的选项,或者可能有一些特殊的命令替换语法,不会删除换行符(后者更好)?
答案1
没有选项可以直接执行此操作(据我所知),但您可以通过在命令替换中添加保护性非换行符来伪造它,然后将其删除:
var="$(somecommand; echo .)" # Add a "." (and one newline that command
# substitution will remove) *after* the
# newline(s) we want to protect
var="${var%.}" # Remove the "." from the end
请注意, 的退出状态somecommand
在进程中丢失,因为echo .
和都var=${var%.}
设置了状态(通常为 0)。如果你需要将其保存到最后,你需要添加一些杂耍(归功于 Kusalananda):
var="$(somecommand; err=$?; echo .; exit "$err")" # Make the subshell
# exit with somecommand's
# status
commandstatus=$? # Preserve the exit status of the subshell
var="${var%.}"
# You can now test $commandstatus to see if the command succeeded
请注意,err
和commandstatus
变量都存储命令的退出状态,但它们不是多余的,因为err
仅存在于 和 创建的子 shell 中$( )
,并且commandstatus
仅存在于父 shell 中。您可以对两者使用相同的名称,但这可能会增加混乱。
顺便说一句,如果您不需要在“.”之后保留状态。已修剪,可以跳过该commandstatus
部分:
if var="$(somecommand; err=$?; echo .; exit "$err")"; then
# somecommand succeeded; proceed with processing
var="${var%.}"
dosomethingwith "$var"
else
echo "somecommand failed with exit status $?" >&2
fi
请注意,根据区域设置,您不能仅使用任何字符来代替“.”。 POSIX 保证不会在其他字符的编码中找到“.”的编码,但并非所有情况都是如此。例如,在实践中,“x”的编码位于 BIG5 或 GB18030 编码中许多字符的编码末尾,因此如果附加“x”而不是“.”,则“x”可能会结束up 与恰好出现在输出末尾的另一个字节组合形成一个新字符,然后“${var%x}”将无法删除该“x”。
答案2
据我所知,唯一具有命令替换且可以被告知不要删除尾随换行符的 shell 是rc
(Unix V10 和 plan9 的 shell,以及 Byron Rakitzis 的公共域克隆及其衍生物,例如es
or akanga
)和fish
。
在rc
类似贝壳的情况下,
var = `{somecommand}
$ifs
将 的输出中的分隔字存储somecommand
到变量中$var
(变量是 中的数组/列表rc
)。
如果是空的$ifs
( ifs = ()
),则按原样存储输出。分隔符也可以用 指定``(separators){somecommand}
,因此:
var = ``(){somecommand}
但rc
//不太像《谍影重重》es
。akanga
另外,您通常希望删除一换行符。
例如在:
dirname=$(dirname -- "$file")
您确实想要删除 所添加的换行符,但不想删除可能位于目录dirname
末尾的换行符。$file
rc
没有内置的运算符。你需要:
dirname = ``(){dirname -- $file | head -c -1}
(不在标准中head
)。在 中es
,您可以使用~~
模式提取运算符:
nl = '
'
dirname = <={ ~~ ``(){dirname -- $file} *$nl }
这几乎不那么清晰(尽管可以做成一个函数)。
shellfish
命令替换将输出分割成其组成行。
set var (somecommand)
将 的输出行存储somecommand
到数组中$var
(即使是输出末尾的空行也会被保留)。
如果您设置$IFS
为空列表或一个空字符串,则该拆分将被禁用,并且最多会从输出末尾删除一个换行符(至少在fish
IIRC 的当前版本中,多年来在这方面发生了一些变化) )。所以在那里,
begin; set -l IFS; set dir (dirname -- $file); end
是可靠的。
我不知道有任何类似 Bourne 的 shell 可以被告知不要剥离全部尾随换行符。这种行为是 POSIX 所要求的,所有类似 Bourne 的 shell 即使不在 posix 模式下也会这样做(对于那些有 bash 或 zsh 之类的 shell)。
在bash
4.4+ 中,您还可以结合使用进程替换来代替命令替换(或在非交互式实例中readarray
使用带有选项 and 的管道):lastpipe
readarray -td '' var < <(somecommand)
这-d ''
是对 NUL 的分割。bash
无论如何,不支持在其变量中存储 NUL,因此对于命令替换的替换就足够了。
readarray -t lines < <(somecommand)
将 的输出行存储somecommand
到$lines
数组中。然后,您可以用换行符将它们连接起来以重建输出,而无需一拖尾换行符:
IFS=$'\n'
output="${lines[*]}"
但在所有这些方法中,您都会失去 的退出状态somecommand
。
要解决命令替换删除的错误功能全部换行符,一个常见的习惯用法是添加一个非换行符并将其剥离(以及一换行符)之后正如戈登所说。
但为了在不失去存在状态的情况下做到这一点,你会这样:
cmdsubst() { # args: var cmd args
eval "$1"'=$(shift; "$@"; ret=$?; printf .; exit "$ret")'
eval "$1=\${$1%.}; $1=\${$1%'
'}; return $?"
}
这类似于命令替换,只不过最多一换行符被删除。
用作:
cmdsubst dir dirname -- "$file"
代替:
dir=$(dirname -- "$file")
或者对于更复杂的命令:
cmdsubst var eval 'cmd1; for i in a b; do cmd "$i"; done'
答案3
在许多用例中有效的一种简单方法是用双引号将命令替换引起来,并在紧接结束引号之前插入换行符。通过放置换行符里面引号但是外部括号,您将其从命令替换的范围中删除,并且 shell 不会自动删除它。
第一个示例在结束引号之前没有换行符,因此下一个$
提示会立即出现在输出之后。第二个例子有效。
$ printf '%s' "$(date)"
Mon Jan 22 13:39:23 PST 2024$ printf '%s' "$(date)
"
Mon Jan 22 13:39:26 PST 2024
$
由于 shell 会删除带括号的命令替换中的所有尾随换行符,并且脚本随后仅添加一个换行符,因此此方法具有保留的限制只有一个尾随换行符。如果有多个换行符,它们会被压缩为一个。