有没有办法在函数或脚本中获取*实际*(未解释的)shell 参数?

ico*_*ast 3 shell bash quoting

我有一个函数posix,我在 Windows 上的 Git bash shell 中使用它来将 DOS 样式的路径转换为普通的 Unix 样式的路径。由于 DOS 样式的路径使用反斜杠作为分隔符,我必须引用 path 参数以防止 shell 使用反斜杠将下一个字符表示为文字。有没有办法从我的函数内部获取未解释的参数,这样我就不需要引用它了?

这是我的功能,如果有帮助的话:

function posix() {
  echo $1 | sed -e 's|\\|/|g' | sed -re 's|^(.)\:|/\L\1|'
}
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,我欢迎任何关于以与解决引用/shell 解释问题无关的其他方式改进功能的提示的评论。)

Gil*_*il' 6

未解释的 shell 参数是$1$2等。在大多数情况下,您需要将它们的扩展放在双引号中,以避免进一步扩展参数的值。"$@"为您提供所有参数的列表。

例如,如果您想将 shell 脚本的参数传递给您的函数,请像这样调用它:

first_argument_as_filename_in_unix_syntax=$(posix "$1")
Run Code Online (Sandbox Code Playgroud)

双引号是必要的。如果你写posix $1,那么你传递的不是第一个参数的值,而是对第一个参数的值执行分词和通配符的结果。在调用脚本时,您也需要使用正确的引用。例如,如果你用 bash 写这个:

myscript c:\path with spaces\somefile
Run Code Online (Sandbox Code Playgroud)

那么实际的、未解释的参数myscriptwill 是c:path,withspacessomefile。所以不要这样做。

您的posix函数是错误的,同样是因为它在$1. 始终在变量和命令替换周围加上双引号:"$foo", "$(foo)". 与实际上不需要引号的例外情况相比,记住此规则更容易。

echo在某些情况下会自行处理,并且调用外部进程很慢(尤其是在 Windows 上)。您可以在 bash 中进行整个处理。

posix () {
  path="${1//\\//}"
  case "$path" in
    ?:*) drive="${p:0:1}"; drive="${drive,}"; p="/$drive/${p:2}";;
  esac
  printf %s "$p"
}
Run Code Online (Sandbox Code Playgroud)

jw013 提到的 zsh 功能并没有像您认为的那样做。可以放在noglob命令前面,zsh 不会对参数进行globbing(即文件名生成,即通配符的扩展)。例如,在 zsh 中,如果您编写noglob locate *foo*bar*,则locate使用参数调用*foo*bar*。您通常noglob会将内置函数隐藏在别名后面。此功能与您尝试执行的操作无关。


mik*_*erv 5

虽然其他答案可能是正确的,指出您无法通过他们提到的方式接收“未解释的”shell 输入,但他们断然否认这种可能性是错误的。如果您指示 shell 不解释它,那么您当然可以在 shell 解释它之前收到它。简单的 POSIXheredoc使这变得非常简单:

% sed -e 's@\\@/@g' -e 's@\(.\):\(.*\)@/drive/\1\2@' <<'_EOF_'     
> c:\some\stupid\windows\place
> _EOF_
/drive/c/some/stupid/windows/place
Run Code Online (Sandbox Code Playgroud)

编辑1:

为了将这样的字符串作为 shell 参数传递给 shell 函数,您需要将其存储在 shell 变量中。一般来说,你不能简单地var=<<'HEREDOC'不幸,但 POSIX 确实指定了内置-r参数read

% man read
Run Code Online (Sandbox Code Playgroud)

POSIX 程序员手册

...

默认情况下,除非指定 -r 选项,否则反斜杠 ( '\' ) 应充当转义字符,如转义字符 (反斜杠) 中所述。如果标准输入是终端设备并且调用 shell 是交互式的,则在以下情况下 read 将提示输入续行:

  • 除非指定 -r 选项,否则 shell 读取以反斜杠结尾的输入行。

  • 输入新行后,此处文档不会终止。

组合起来后,这readheredoc变得微不足道且便于携带,尽管一开始可能感觉不太直观:

% _stupid_mspath_fix() { 
> sed -e 's@\\@/@g' -e 's@\(.\):\(.*\)@/drive/\1\2@' <<_EOF_
>> ${1}
>> _EOF_
> }
% read -r _stupid_mspath_arg <<'_EOF_'                    
> c:\some\stupid\windows\place
> _EOF_
% _stupid_mspath_fix ${_stupid_mspath_arg}
/drive/c/some/stupid/windows/place
Run Code Online (Sandbox Code Playgroud)

编辑2:

heredocs您可能在第二个示例中注意到了两者之间的区别。heredoc _EOF_函数内的终止符不带引号,而输入的终止符则用read单引号引起来。通过这种方式,shell 被指示heredoc使用未加引号的终止符对 执行扩展,但当其终止符被加引号时则不执行此操作。当扩展函数中未加引号的内容时,它不会中断heredoc,因为它扩展的变量的值已经设置为带引号的字符串,并且不会对其进行两次解析。

您可能想要做的是将 Windows 路径从一个命令的输出动态地传递到另一个命令的输入。a 中的命令替换heredoc使得这成为可能:

% _stupid_mspath_fix() { 
> sed -e 's@\\@/@g' -e 's@\(.\):\(.*\)@/drive/\1\2@' <<_EOF_
>> ${1}
>> _EOF_
> }
% read -r _stupid_mspath_arg <<'_EOF_'                    
> c:\some\stupid\windows\place
> _EOF_
% _stupid_mspath_fix ${_stupid_mspath_arg}
/drive/c/some/stupid/windows/place    
% read -r _second_stupid_mspath_arg <<_EOF_                    
> $(printf ${_stupid_mspath_arg})
> _EOF_
% _stupid_mspath_fix ${_second_stupid_mspath_arg}
/drive/c/some/stupid/windows/place
Run Code Online (Sandbox Code Playgroud)

因此,基本上,如果您可以可靠地从某个应用程序输出反斜杠(我printf在上面使用过),那么在内部运行该命令$(...)并将其包含在传递heredoc给另一个可以可靠地接受反斜杠作为输入的应用程序(例如read上面的应用程序sed)的未加引号中,将绕过shell 完全解析你的反斜杠。应用程序是否可以将反斜杠作为输入/输出处理,您必须自己找出答案。

与问题不严格相关:

在 Gilles 的回答中,他推荐了${var/search/replace}参数扩展形式,虽然很酷,但不是 POSIX。这绝对是一种羞辱。这对我来说并不重要,但在他的编辑中,他保留了posix ()函数名称,这可能会误导某些人。

在这一点上,原始帖子的posix ()函数使用了非常方便的扩展正则表达式sed -r参数,但不幸的是,这也不是 POSIX。POSIX 没有为 指定扩展正则表达式参数sed,因此它的使用可能不可靠。

我在 stack Overflow 上的帐户也只有几天的历史,但我在那里发布了一些专门处理 POSIX 参数扩展的答案,您可以从我的个人资料页面找到链接到这些答案,其中我包含来自 POSIX 的引用指南及其链接。您还会发现我在其中演示了 的其他用法heredoc,例如将整个 shell 脚本读入 shell 变量,以编程方式解析和操作它,然后最终运行其新版本,所有这些都在另一个脚本或 shell 函数中完成。只是说。