通过 ssh 包装命令:如何管理复杂的引号?
wrapping commands through ssh: how to manage complex quotes?
我使用 HPC 集群。计算节点无法访问互联网,只能访问正面。
所以我想包装所有需要访问互联网的命令,以便在正面执行它们。
例如:对于 wget
#!/bin/bash
ssh frontal /bin/wget "$@"
-> 工作正常
我必须包装这个 bq (google BigQuery) 命令:
bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
我设法重新引用命令并在 CLI 上成功启动它:
ssh frontal '~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '"'"'2016%'"'"' AND mgrs_tile == '"'"'32ULU'"'"' ORDER BY sensing_time ASC LIMIT 1000;"'
现在我想编写一个名为 bq 的包装器,能够获取参数并通过 ssh 启动此命令...这是我尝试过的方法:
#!/bin/bash
set -eu
# all parameters in an array
args=("$@")
# unset globing (there's a * in the SELECT clause)
set -f
# managing inner quotes
arg2=`echo "${args[2]}" | perl -pe 's/'\''/'\''"'\''"'\''/g'`
# put back double quotes (") suppressed by bash
args="${args[0]} ${args[1]} \"${arg2}\""
# build command with parameters
cmd="~/downloads_and_builds/builds/google-cloud-sdk/bin/bq $args"
echo ""
echo "command without external quotes"
echo "$cmd"
echo ""
echo "testing it ..."
ssh hpc-login1 "$cmd"
echo ""
# wrapping command between simple quotes (like on the CLI)
cmd="'"'~/downloads_and_builds/builds/google-cloud-sdk/bin/bq '"$args""'"
echo "commande with external quotes"
echo "$cmd"
echo ""
echo "testing it ..."
ssh hpc-login1 $cmd
echo "done"
下面是这个脚本的输出:
$ bq --format=json 查询 "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
command without external quotes
~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '"'"'2016%'"'"' AND mgrs_tile == '"'"'32ULU'"'"' ORDER BY sensing_time ASC LIMIT 1000;"
testing it ...
Waiting on bqjob_r102b0c22cdd77c2d_000001629b8391a3_1 ... (0s) Current status: DONE
commande with external quotes
'~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '"'"'2016%'"'"' AND mgrs_tile == '"'"'32ULU'"'"' ORDER BY sensing_time ASC LIMIT 1000;"'
testing it ...
bash: ~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;": Aucun fichier ou dossier de ce type (in english: no file or directory of this kind)
如您所见,我设法获得了正确的命令字符串,就像在 CLI 上运行的命令字符串一样,但它在我的脚本中不起作用:
- 第一次尝试成功但没有输出(我试图将其重定向到文件中:文件已创建但为空)
- 在第二次尝试中(使用外部单引号,就像有效的 CLI 命令一样),bash 将引用的 arg 作为一个块,但没有找到命令 ...
有人知道如何使用包装脚本通过 ssh 启动复杂命令(带引号、通配符...)吗?
(即 一个名为 foo 的包装器 能够替换 foo 命令并使用提供的参数通过 ssh 正确执行它)
基本方法,使用 shell 此处 doc :
#!/bin/bash
ssh -t server<<'EOF'
bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
command2
command3
...
EOF
ssh
与 eval
具有相同的语义:所有参数都用空格连接,然后作为 shell 命令求值。
您可以通过包装器转义参数使其与 execve
语义(如 sudo)一起工作:
remotebq() {
ssh yourhost "~/downloads_and_builds/builds/google-cloud-sdk/bin/bq $(printf '%q ' "$@")"
}
此引用彻底且一致,因此您不必再担心添加额外的转义。它会 运行 完全按照你说的去做(只要你的遥控器 shell 是 bash
):
remotebq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
然而,运行宁其所言的不利之处在于,现在您需要确切地知道自己想要什么 运行。
例如,您不能再将 '~/foo'
作为参数传递,因为这不是有效文件:~
是 shell 功能而不是目录名称,并且当它已正确转义,不会被您的主目录替换。
我看到您已经在使用 Perl,所以...
use Net::OpenSSH;
my $query = q(SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;);
my $ssh = Net::OpenSSH->new($host);
$ssh->system('bq', '--format=json', 'query', $query)
or die $ssh->error;
Net::OpenSSH
会负责引用所有内容。
我使用 HPC 集群。计算节点无法访问互联网,只能访问正面。
所以我想包装所有需要访问互联网的命令,以便在正面执行它们。
例如:对于 wget
#!/bin/bash
ssh frontal /bin/wget "$@"
-> 工作正常
我必须包装这个 bq (google BigQuery) 命令:
bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
我设法重新引用命令并在 CLI 上成功启动它:
ssh frontal '~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '"'"'2016%'"'"' AND mgrs_tile == '"'"'32ULU'"'"' ORDER BY sensing_time ASC LIMIT 1000;"'
现在我想编写一个名为 bq 的包装器,能够获取参数并通过 ssh 启动此命令...这是我尝试过的方法:
#!/bin/bash
set -eu
# all parameters in an array
args=("$@")
# unset globing (there's a * in the SELECT clause)
set -f
# managing inner quotes
arg2=`echo "${args[2]}" | perl -pe 's/'\''/'\''"'\''"'\''/g'`
# put back double quotes (") suppressed by bash
args="${args[0]} ${args[1]} \"${arg2}\""
# build command with parameters
cmd="~/downloads_and_builds/builds/google-cloud-sdk/bin/bq $args"
echo ""
echo "command without external quotes"
echo "$cmd"
echo ""
echo "testing it ..."
ssh hpc-login1 "$cmd"
echo ""
# wrapping command between simple quotes (like on the CLI)
cmd="'"'~/downloads_and_builds/builds/google-cloud-sdk/bin/bq '"$args""'"
echo "commande with external quotes"
echo "$cmd"
echo ""
echo "testing it ..."
ssh hpc-login1 $cmd
echo "done"
下面是这个脚本的输出: $ bq --format=json 查询 "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
command without external quotes
~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '"'"'2016%'"'"' AND mgrs_tile == '"'"'32ULU'"'"' ORDER BY sensing_time ASC LIMIT 1000;"
testing it ...
Waiting on bqjob_r102b0c22cdd77c2d_000001629b8391a3_1 ... (0s) Current status: DONE
commande with external quotes
'~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '"'"'2016%'"'"' AND mgrs_tile == '"'"'32ULU'"'"' ORDER BY sensing_time ASC LIMIT 1000;"'
testing it ...
bash: ~/downloads_and_builds/builds/google-cloud-sdk/bin/bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;": Aucun fichier ou dossier de ce type (in english: no file or directory of this kind)
如您所见,我设法获得了正确的命令字符串,就像在 CLI 上运行的命令字符串一样,但它在我的脚本中不起作用:
- 第一次尝试成功但没有输出(我试图将其重定向到文件中:文件已创建但为空)
- 在第二次尝试中(使用外部单引号,就像有效的 CLI 命令一样),bash 将引用的 arg 作为一个块,但没有找到命令 ...
有人知道如何使用包装脚本通过 ssh 启动复杂命令(带引号、通配符...)吗?
(即 一个名为 foo 的包装器 能够替换 foo 命令并使用提供的参数通过 ssh 正确执行它)
基本方法,使用 shell 此处 doc :
#!/bin/bash
ssh -t server<<'EOF'
bq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
command2
command3
...
EOF
ssh
与 eval
具有相同的语义:所有参数都用空格连接,然后作为 shell 命令求值。
您可以通过包装器转义参数使其与 execve
语义(如 sudo)一起工作:
remotebq() {
ssh yourhost "~/downloads_and_builds/builds/google-cloud-sdk/bin/bq $(printf '%q ' "$@")"
}
此引用彻底且一致,因此您不必再担心添加额外的转义。它会 运行 完全按照你说的去做(只要你的遥控器 shell 是 bash
):
remotebq --format=json query "SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;"
然而,运行宁其所言的不利之处在于,现在您需要确切地知道自己想要什么 运行。
例如,您不能再将 '~/foo'
作为参数传递,因为这不是有效文件:~
是 shell 功能而不是目录名称,并且当它已正确转义,不会被您的主目录替换。
我看到您已经在使用 Perl,所以...
use Net::OpenSSH;
my $query = q(SELECT * FROM [bigquery-public-data:cloud_storage_geo_index.sentinel_2_index] WHERE sensing_time LIKE '2016%' AND mgrs_tile == '32ULU' ORDER BY sensing_time ASC LIMIT 1000;);
my $ssh = Net::OpenSSH->new($host);
$ssh->system('bq', '--format=json', 'query', $query)
or die $ssh->error;
Net::OpenSSH
会负责引用所有内容。