可能会受到打击吗?将命令扩展转换为参数


0

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

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

我试图用bash复制我创建的要在Mac OS上使用的鱼壳函数。我的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用户只是避免在文件名中使用空格或符号(例如动物)来简化一切?感谢您的任何答案。

Answers:


2

有多种方法可以做您想要的。默认情况下,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'"

从“ $ {i} .file”更改为“ $ {i} x.file”……您在说可以使用空格吗?; ^)
皮埃尔·休斯顿

而且我希望通过命令行(而不是脚本)这样做是为了方便,所以不适合使用样板设置IFS等。但是无论如何,特别感谢您对我的特定问题的直接回答
皮埃尔·休斯顿,

@PierreHouston错误已修复:)))
Alex

@PierreHouston对于命令行操作(不制作脚本),请使用variant#2,只需将带有;字符的命令分开即可
Alex

不错的快速错误修复:thumbsup:。我完全看不到#2对我有什么帮助。我(几乎)从字面上想在bash提示符下输入:cmda $(cmdb-that-generates-parameters-for-cmda),一个更真实的示例:transcode $(selection)。似乎不可能,我将在我的工作Mac上开始使用鱼壳。
皮埃尔·休斯顿,

1

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 语法那样简单。但是,您可以使用自定义函数来简化any(?)解决方案的语法,例如:

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

_ ls selection如果仅selection生成xargs正确的输入,则允许您调用。更好的是:

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

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

不过,这还不如鱼肉的灵活和优雅ls (selection)。我希望你可以做ls (selection1) (selection2)鱼;上面的_功能并不涵盖这种情况。

另一方面,eval ls $(selection1) $(selection2)如果selection1selection2返回的字符串使用正确的引号和/或转义符进行了消毒,则可能会起作用。如果攻击者可以使您选择一个名为的文件; rm -rf ~/;,则最好对其进行清理。或不eval首先使用。


没错,我并不是在谈论bash,而是在unix-y系统(尤其是Mac OS)的终端应用程序中使用bash shell时的命令行可用性。因此,所有迹象都表明不,bash不能做到这一点。谢谢。
皮埃尔·休斯顿,

的意思是说.. bash不使用eval
皮埃尔·休斯顿,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.