【问题标题】:Quoting and escaping for mix cmd aliases混合 cmd 别名的引用和转义
【发布时间】:2021-03-19 16:05:19
【问题描述】:

原则上,通过在mix.exs 中定义以cmd 开头的aliases,我可以将任何shell 命令作为混合任务运行。但我在空格、引用和反斜杠转义方面苦苦挣扎。以下是一些无效的示例:

defp aliases do
  f1: ["cmd echo \"two  spaces  between\""],
  f2: ["cmd echo 'two  spaces  between'"],
  f3: ["cmd echo two\ \ spaces\ \ between"],
  f4: ["cmd echo two\\ \\ spaces\\ \\ between"],
end

他们都只是产生字符串"two spaces between",不是我期望或目标的,"two spaces between"

$ for t in f1 f2 f3 f4; do mix $t; done
two spaces between
two spaces between
two spaces between
two spaces between

那么如何将带引号的字符串传递给 shell 命令呢?更一般地说,别名cmd 目标在被shell 运行之前是如何处理的,还是直接执行的?命令字符串中去掉单双引号和反斜杠的逻辑是什么?

更新:更多失败案例:

  q1: ["cmd echo \"one  two  three\""],         # "one two three"
  q3: ["cmd echo \\\"one  two  three\\\""],     # "one two three"
  q5: ["cmd echo \\\\\"one  two  three\\\\\""], # "one" (!!!)

  f5: [
    """
    cmd echo 'two  spaces  between'
    """
      ],

显然,我希望能够同时传递两种​​引号以获得预期效果,但似乎有一些未记录在案的情况,并且在这种情况下,操作中的输入字符串过于激进和不必要的卫生处理。

我正在使用 Elixir 1.11.1 运行它。

【问题讨论】:

  • 我相信有些引号被壳本身吃掉了。在[~S|cmd echo \\"two spaces between\\"|] 旁边应该做一些事情。
  • 在发布原始问题之前,我已经尝试过增加反斜杠的数量,但总是失败。你的相当于["cmd echo \\\\\"two spaces between\\\\\""],对吧?有了这 5 个随之而来的反斜杠,我只打印了“一个”。更奇怪的是。

标签: elixir elixir-mix


【解决方案1】:

谜团解开

发生了什么事?它是关于如何解析任务参数解决方案是在cmd 之后引用整个 shell 命令,以将其传递给 shell,而不会发生意外的变形

列出的别名 f1f2f3f4 具有非预期效果

defp aliases do                                # BAD!!
  f1: ["cmd echo \"two  spaces  between\""],
  f2: ["cmd echo 'two  spaces  between'"],
  f3: ["cmd echo two\ \ spaces\ \ between"],
  f4: ["cmd echo two\\ \\ spaces\\ \\ between"]
end

相当于从 shell 运行

$ mix cmd echo "two  spaces  between"           # BAD!!!
$ mix cmd echo 'two  spaces  between'
$ mix cmd echo two  spaces  between
$ mix cmd echo two\ \ spaces\ \ between

他们都输出two spaces between

跑步

$ mix cmd "echo \"two  spaces  between\""       # GOOD!!!
$ mix cmd "echo 'two  spaces  between'"
$ mix cmd "echo two \\ spaces \\ between"

给出预期的输出one two three

所以,答案是 shell 命令应该全部引用,如下所示:

defp aliases do                                 # GOOD!!!
  ok1: ["cmd \"echo \\\"two  spaces  between\\\"\""],
  ok2: ["cmd \"echo 'two  spaces  between'\""],
  ok3: ["cmd \"echo two\\ \\ spaces\\ \\ between\""],
end

或等效地(使用 Aleksei 的~S sigil 来简化引用):

defp aliases() do                               # GOOD!!!
  ok1: [~S|cmd "echo \"two  spaces  between\""|],
  ok2: [~S|cmd "echo 'two  spaces  between'"|],
  ok3: [~S|cmd "echo two\ \ spaces\ \ between"|],
end

然后一切都像想象的那样工作:

$ for t in ok1 ok2 ok3; do mix $t; done
two  spaces  between
two  spaces  between
two  spaces  between

在窗帘后面偷看

查看elixir源码,文件lib/mix/lib/mix/tasks/cmd.ex模块Mix.Tasks.Cmd函数run/1可以看到命令是如何传递给shell的。基本上是这样的:

Mix.shell().cmd(Enum.join(args, " ")

其中argsmix cmd 之后的shell 命令参数。

所以命令 mix cmd echo 'two spaces' 或别名目标 ["cmd echo 'two spaces'] 导致调用 Mix.Tasks.Cmd.run(["echo", "two spaces"]) 并运行

Mix.shell().cmd("echo two  spaces")

当然,shell 并不关心单词之间有多少空格。工作的mix cmd "echo 'two spaces'" 或别名目标["cmd \"echo 'two spaces'\""] 调用Mix.Tasks.Cmd.run(["echo 'two spaces']) 并组合为

Mix.shell().cmd("echo 'two  spaces'")

给出预期的效果。

更有用的例子

我正在努力完成的实际问题的解决方案:

defp aliases() do
  "check-formatted": [~S/cmd "bash -c 'mix format --check-formatted $(git ls-tree -r --name-only HEAD | egrep \"[.]exs?$\")'"/],
  # ...
end

(在我的机器上,这需要通过 bash 运行以生成 mix format 的文件名列表,而不是一大串文件名)。

这很快就变得丑陋了,所以对于更复杂的场景,@zwippie 使用 Elixir 函数定义别名的解决方案,其中使用 Mix.shell().cmd() 运行需要通过运行外部程序完成的步骤确实是更好的方法.

【讨论】:

  • 我不会删除我的答案,因为 jours 指的是它。 +1
  • @AlekseiMatiushkin ~S sigil 确实简化了引用...我(新手长生不老药)会记住它! ;-)
【解决方案2】:

引号被shell自己吃掉了,我这里一直只擅长猜测:)

显然,这是可行的:

foo: [~S|cmd echo \\\"two\\ \\ spaces\\ \\ between\\\"|]

mix foo
#⇒ "two  spaces  between"

【讨论】:

  • 最后的单词之间不是有两个空格吗?
  • @Daniel gosh,谢谢,更新了答案;我复制粘贴有些错误。
  • 查看我的答案以了解正在发生的事情(以及引用被吃掉的地方)。
【解决方案3】:

不知道这是否适用于您的情况,但是当您在本地函数中定义任务时,它似乎工作得更好:

  defp aliases do
    [
      f1: &f1/1,
      f2: &f2/1
    ]
  end

  defp f1(_) do
    Mix.shell().cmd("echo 'two  spaces  between'")
  end

  # or if you just want to display some text
  defp f2(_) do
    Mix.shell().info("two  spaces  between")
  end

输出:

$for t in f1 f2; do mix $t; done
two  spaces  between
two  spaces  between

【讨论】:

  • 这是一个实用的解决方案,它允许运行任何 shell 命令。尽管了解通过 cmd 前缀字符串传递 shell 命令的尝试仍然是一个很大的谜。
猜你喜欢
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 2014-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-24
相关资源
最近更新 更多