变体#1

变体#1

是 bash 命令扩展的唯一选项:

  • 不带引号$(..),其输出总是用愚蠢的字符串分割来解析,或者
  • 引用"$(..)"其输出总是作为一个单位传递?

我正在尝试在 Bash 中复制我为 Mac OS 创建的 fish shell 函数。我的 fish 函数selection https://superuser.com/a/1165855在最前面的窗口中进行选择并输出用于命令替换的路径,例如ls -l (selection)。我希望在 bash 中实现同样的事情,也许就像这样ls -l $(selection)

我以为这是引用的问题,所以尝试将换行符分隔的路径传递给 bash 的printf "%q "。但是我发现,无论我用什么引用来包装命令替换输出,它都会在空格处被分割。

简化示例:

$ touch file\ a file\ b
$ ls $( echo "file\ a file\ b" ) # want expansion equiv to: ls 'file a' 'file b'
ls: a: No such file or directory
ls: b: No such file or directory
ls: file\: No such file or directory
ls: file\: No such file or directory

如果我不得不使用带引号的命令替换,那也不算世界末日,ls -l "$(selection)"但这样做命令的输出永远不会被拆分,更不用说遵守我仔细的引用了。旧的反引号语法有什么不同吗?

有趣的是,bash,你有很多功能。但是没有人允许吗cmda $(cmdb-that-generates-parameters-for-cmda)?或者 bash 用户只是避免在文件名中使用任何空格或符号(如动物)以使一切变得简单?谢谢大家的回答。

答案1

有多种方法可以实现你想要的效果。默认情况下bash使用空格作为默认分隔符,但您可以使用IFS(内部字段分隔符)轻松覆盖它,或使用其他技术(例如枚举)。以下是首先想到的几个例子。

变体#1

使用特殊内部变量IFS(内部字段分隔符)

#!/bin/bash

touch 'file a' 'file b'

# Set IFS to new line as a separator
IFS='
'
ls -la $( echo 'file a'; echo 'file b' )

变体#2

使用for循环

#!/bin/bash

touch 'file a' 'file b'
for variable in 'file a' 'file b';do
  ls -la "${variable}"
done

变体#3

使用for预定义值的循环

#!/bin/bash

MultiArgs='
arg 0 1
arg 0  2
arg 0   3
arg 0    N
'

# Using only new line as a separator
IFS='
'

for variable in ${MultiArgs};do
  touch "${variable}"
  ls -la "${variable}"
done

变体#4

使用~(tilda)作为分隔符

#!/bin/bash

MultiArgs='arg 0 1~arg 0 2~arg0 3~ar g 0 N' # Arguments with spaces

IFS='~'
for file in ${MultiArgs};do
 touch "${file}"
 ls -la "${file}";
done

旧的反引号语法有什么不同吗?

不,它们是相同的,但是反引号有一些限制$()

或者 bash 用户只是避免在文件名中使用任何空格或符号(如动物)以使一切变得简单?

不,只要使用正确的引用,在文件名中使用空格是可以的。

关于$(cmdb-that-generates-parameters-for-cmda)

#!/bin/bash

# first command generate text for `sed` command that replacing . to ###
echo $( for i in {1..5}; do echo "${i} space .";done | sed 's/\./###/g' )

#############################################
# Another example: $() inside of another $()
for i in {1..5}; do touch "${i} x.file";done # Prepare some files with spaces in filenames

IFS='
'
echo "$( ls -la $(for i in ls *.file; do echo "$i"; done))"

如果您希望在一行中传递所有参数来提供给程序,transcode您可以在文件末尾添加~/.bashrc 以下函数:

_tr() {
  local debug
  debug=1
  [ $debug -eq 1 ] && {
    echo "Number of Arguments: $#"
    echo "Arguments: $@"
  }

  transcode "$@"
}

并从命令行调用此函数,如下所示:

eval _tr $(echo "$out")

其中变量out必须是这样的:out="'file a' 'file b'"
如果手动输入文件名,那么_tr函数调用可能看起来像这样:

eval _tr $(echo "'file a' 'file b'")

如果您要使用某些外部脚本来代替$(),那么外部脚本/程序必须返回如下所示的文件列表:

"'file a' 'file b'"

答案2

xargs

echo '"file a" "file b"' | xargs ls

注意引号。注意xargs这不是 Bash 内置函数(我是在“Bash,你有很多功能”的上下文中提到这一点的)。

eval是 Bash 的内置命令:

eval ls $( echo '"file a" "file b"' )

再次提醒,注意引号。这可能很容易适得其反. 基于改变IFS另一个答案)似乎更安全。另一方面,eval你甚至可以这样做:

eval ls $( echo '"file "{a,b}' )

不幸的是,这些都不如ls (selection)您开始使用的其他 shell 语法那么简单。但是,您可以使用自定义函数简化任何解决方案的语法,例如:

_(){ "$2" | xargs "$1"; }

这允许您调用_ ls selection,如果仅selection生成xargs右侧的输入。甚至更好的是,这:

_(){ "${@:$#}" | xargs "${@:1:$(($#-1))}"; }

使之成为可能。我想您可以创建一个类似的函数,使用另一个答案中的_ ls -l "file c" selection“更改”方法。IFS

尽管如此,这还是不如 fish 的灵活和优雅ls (selection)。我希望你可以ls (selection1) (selection2)在 fish 中做到这一点;上面的_函数不包括这种情况。

另一方面,如果返回字符串,则eval ls $(selection1) $(selection2)可能会起作用selection1selection2已消毒使用正确的引号和/或转义。如果攻击者可以让你选择一个名为的文件,; rm -rf ~/;你最好好好清理一下;或者eval一开始就不要使用。

相关内容