是 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)
可能会起作用selection1
selection2
已消毒使用正确的引号和/或转义。如果攻击者可以让你选择一个名为的文件,; rm -rf ~/;
你最好好好清理一下;或者eval
一开始就不要使用。