eval printf可从命令行运行,但不能在脚本中运行

时间:2019-07-06 00:01:58

标签: shell

当我在终端中运行以下命令时,它可以运行,但不能从脚本中运行:

eval $(printf "ssh foo -f -N "; \
       for port in $(cat ~/bar.json | grep '_port' | grep -o '[0-9]\+'); do \
           printf "-L $port:127.0.0.1:$port ";\
       done)

我得到的错误告诉我,printf的用法是错误的,好像引号内的-L参数是printf本身的参数。 我想知道为什么会这样。我缺少明显的东西吗?

__

上下文(如果我的问题是XY问题):我想启动并连接到在远程计算机上运行的jupyter内核。为此,我编写了一个小脚本

  1. 每个ssh发送一个命令,以供远程启动内核
  2. 通过scp复制一个配置文件,我可以使用该文件从本地计算机连接到内核
  3. 读取配置文件并在本地和远程之间打开适当的ssh隧道

对于不熟悉jupyter的用户来说,配置文件(bar.json)看起来大致如下:

{
  "shell_port": 35932,
  "iopub_port": 37145,
  "stdin_port": 42704,
  "control_port": 39329,
  "hb_port": 39253,
  "ip": "127.0.0.1",
  "key": "4cd3e12f-321bcb113c204eca3a0723d9",
  "transport": "tcp",
  "signature_scheme": "hmac-sha256",
  "kernel_name": ""
}

因此,在上面的命令中,printf语句创建一个ssh命令,其中包含所有5 -L端口转发,以便我的本地计算机连接到远程计算机,并且eval应该运行该命令。这是完整的脚本:

#!/usr/bin/env bash

# Tell remote to start a jupyter kernel.
ssh foo -t 'python -m ipykernel_launcher -f ~/bar.json' &
# Wait a bit for the remote kernel to launch and write conf. file
sleep 5
# Copy the conf. file from remote to local.
scp foo:~/bar.json ~/bar.json
# Parse the conf. file and open ssh tunnels.
eval $(printf "ssh foo -f -N "; \
       for port in $(cat ~/bar.json | grep '_port' | grep -o '[0-9]\+'); do \
           printf "-L $port:127.0.0.1:$port ";\
       done)

最后,jupyter console --existing ~/foo.json连接到远程计算机。

1 个答案:

答案 0 :(得分:3)

正如@other所说的那样,bash的printf内置printf "-L ..."上的barfs。它认为您正在传递一个-L选项。您可以通过添加--来解决此问题:

printf -- "-L $port:127.0.0.1:$port "

让我们做到这一点

printf -- '-L %s:127.0.0.1:%s ' "$port" "$port"

但是既然我们在这里,我们可以做得更好。首先,我们不要使用基本的shell工具来处理JSON。我们不想依靠它以某种方式进行格式化。我们可以使用jq,这是一种轻巧灵活的命令行JSON处理器。

$ jq -r 'to_entries | map(select(.key | test(".*_port"))) | .[].value' bar.json
35932
37145
42704
39329
39253

在这里,我们使用to_entries将每个字段转换为键值对。然后我们选择条目,其中.key与正则表达式.*_port相匹配。最后,我们提取相应的.value

我们可以通过在数组中构造eval命令来摆脱ssh。尽可能避免使用eval

#!/bin/bash

readarray -t ports < <(jq -r 'to_entries | map(select(.key | test(".*_port"))) | .[].value' bar.json)

ssh=(ssh foo -f -N)
for port in "${ports[@]}"; do ssh+=(-L "$port:127.0.0.1:$port"); done
"${ssh[@]}"