Bash 选项使命令替换保留尾随换行符

Bash 选项使命令替换保留尾随换行符

我希望它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

请注意,errcommandstatus变量都存储命令的退出状态,但它们不是多余的,因为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 的公共域克隆及其衍生物,例如esor akanga)和fish

rc类似贝壳的情况下,

var = `{somecommand}

$ifs将 的输出中的分隔字存储somecommand到变量中$var(变量是 中的数组/列表rc)。

如果是空的$ifs( ifs = ()),则按原样存储输出。分隔符也可以用 指定``(separators){somecommand},因此:

var = ``(){somecommand}

rc//不太像《谍影重重》esakanga

另外,您通常希望删除换行符。

例如在:

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为空列表或一个空字符串,则该拆分将被禁用,并且最多会从输出末尾删除一个换行符(至少在fishIIRC 的当前版本中,多年来在这方面发生了一些变化) )。所以在那里,

begin; set -l IFS; set dir (dirname -- $file); end

是可靠的。

我不知道有任何类似 Bourne 的 shell 可以被告知不要剥离全部尾随换行符。这种行为是 POSIX 所要求的,所有类似 Bourne 的 shell 即使不在 posix 模式下也会这样做(对于那些有 bash 或 zsh 之类的 shell)。

bash4.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 会删除带括号的命令替换中的所有尾随换行符,并且脚本随后仅添加一个换行符,因此此方法具有保留的限制只有一个尾随换行符。如果有多个换行符,它们会被压缩为一个。

相关内容