如何编写 bash 函数来打印和 运行 命令,当命令的参数带有空格或要扩展的内容时
How to write bash function to print and run command when the command has arguments with spaces or things to be expanded
在 Bash 脚本中,我经常发现这种模式很有用,我首先打印我将要执行的命令,然后执行命令:
echo 'Running this cmd: ls -1 "$HOME/temp/some folder with spaces'
ls -1 "$HOME/temp/some folder with spaces"
echo 'Running this cmd: df -h'
df -h
# etc.
注意 echo
命令中的 单引号 以防止那里的变量扩展!我的想法是我想打印我正在 运行ning 的 cmd, 完全按照我输入的方式打印 运行 命令 ,然后 运行它!
如何将其封装到一个函数中?
将命令包装到标准 bash 数组中,然后打印并调用它,像这样,sort-of 有效:
# Print and run the passed-in command
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run_cmd cmd_array
# See:
# 1. My answer on how to pass regular "indexed" and associative arrays by reference:
# and
# 1. My answer on how to pass associative arrays:
print_and_run_cmd() {
local -n array_reference=""
echo "Running cmd: ${cmd_array[@]}"
# run the command by calling all elements of the command array at once
${cmd_array[@]}
}
对于像这样的简单命令,它工作正常:
用法:
cmd_array=(ls -a -l -F /)
print_and_run_cmd cmd_array
输出:
Running cmd: ls -a -l -F /
(all output of that cmd is here)
但是对于更复杂的命令,它被破坏了!:
用法:
cmd_array=(ls -1 "$HOME/temp/some folder with spaces")
print_and_run_cmd cmd_array
期望的输出:
Running cmd: ls -1 "$HOME/temp/some folder with spaces"
(all output of that command should be here)
实际输出:
Running cmd: ls -1 /home/gabriel/temp/some folder with spaces
ls: cannot access '/home/gabriel/temp/some': No such file or directory
ls: cannot access 'folder': No such file or directory
ls: cannot access 'with': No such file or directory
ls: cannot access 'spaces': No such file or directory
第一个问题,如您所见,$HOME
在 Running cmd:
行中得到了扩展,而这是不应该的,并且该路径参数周围的双引号已被删除, 第二个问题 是该命令实际上并不 运行.
如何解决这 2 个问题?
参考文献:
- 我的 bash 演示程序,我有这个
print_and_run_cmd
函数:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/argument_parsing__3_advanced__gen_prog_template.sh
- 我首先记录了如何通过引用传递 bash 数组,就像我在该函数中所做的那样:
- Passing arrays as parameters in bash
- How to pass an associative array as argument to a function in Bash?
后续问题:
- Bash: how to print and run a cmd array which has the pipe operator, |, in it
检查您的脚本 shellcheck:
Line 2:
local -n array_reference=""
^-- SC2034 (warning): array_reference appears unused. Verify use (or export if used externally).
Line 3:
echo "Running cmd: ${cmd_array[@]}"
^-- SC2145 (error): Argument mixes string and array. Use * or separate argument.
^-- SC2154 (warning): cmd_array is referenced but not assigned.
Line 5:
${cmd_array[@]}
^-- SC2068 (error): Double quote array expansions to avoid re-splitting elements.
您可能想研究一下 https://github.com/koalaman/shellcheck/wiki/SC2068。我们修复了所有错误并得到:
print_and_run_cmd() {
local -n array_reference=""
echo "Running cmd: ${array_reference[*]}"
# run the command by calling all elements of the command array at once
"${array_reference[@]}"
}
对我来说,在这种情况下通过引用传递数组很奇怪。我会传递实际值。我经常这样做:
prun() {
# in the style of set -x
# print to stderr, so output can be captured
echo "+ $*" >&2
# or echo "+ ${*@Q}" >&2
# or echo "+$(printf " %q" "$@")" >&2
# or echo "+$(/bin/printf " %q" "$@")" >&2
"$@"
}
prun "${cmd_array[@]}"
How do I fix these 2 problems?
将 linters、格式化程序和静态分析工具(例如 shellcheck)合并到您的工作流程中,并检查它们指出的问题。
并引用变量扩展。这是 "${array[@]}"
.
您可以使用 DEBUG 陷阱实现您想要的:
#!/bin/bash
set -T
trap 'test "$FUNCNAME" = print_and_run_cmd || trap_saved_command="${BASH_COMMAND}"' DEBUG
print_and_run_cmd(){
echo "Running this cmd: ${trap_saved_command#* }"
"$@"
}
outer(){
print_and_run_cmd ls -1 "$HOME/temp/some folder with spaces"
}
outer
# output ->
# Running this cmd: ls -1 "$HOME/temp/some folder with spaces"
# ...
这似乎也有效:
print_and_run_cmd() {
echo "Running cmd: "
eval "$cmd"
}
cmd='ls -1 "$HOME/temp/some folder with spaces"'
print_and_run_cmd "$cmd"
输出:
Running cmd: ls -1 "$HOME/temp/some folder with spaces"
(result of running the cmd is here)
但现在的问题是,如果我也想打印一个 expanded 版本的命令,以验证该部分是否正常工作,我不能,或者至少,不知道怎么办。
如果您有 Bash 4.4 或更高版本,此函数可能会满足您的要求:
function print_and_run_cmd
{
local PS4='Running cmd: '
local -
set -o xtrace
"$@"
}
例如,运行
print_and_run_cmd echo 'Hello World!'
产出
Running cmd: echo 'Hello World!'
Hello World!
当 xtrace
选项打开时,local PS4='Running cmd: '
为 shell 打印的命令设置前缀。默认值为 +
。本地化是指函数returns.
时自动恢复PS4
之前的值
local -
导致对 shell 选项的任何更改在函数 returns 时自动还原。特别是,当函数 returns 时,它会导致下一行的 set -o xtrace
自动撤消。在 Bash 4.4.
中添加了对 local -
的支持
来自 man bash
,在 local [option] [name[=value] ... | - ]
部分下(强调已添加):
If name is -
, the set of shell options is made local to the function in which local is invoked: shell options changed using the set
builtin inside the function are restored to their original values when the function returns.
set -o xtrace
(相当于set -x
)导致shell打印命令,前面是PS4
的扩展值,之前运行 他们。
参见 help set
。
我很喜欢,所以我把它标记为正确。虽然它没有完全回答我原来的问题,所以如果出现另一个答案,我可能不得不改变它。 无论如何,请参阅@pjh 的回答或下面代码如何工作的完整解释,以及所有这些行的含义。我已经使用 man bash
和 help set
.
中的一些来源帮助编辑了该答案
不过,我想更改格式并提供更多示例,以表明命令中确实发生了变量扩展。我还想提供一个通过引用的版本,一个不通过引用的版本,这样你就可以选择你最喜欢的调用方式。
这是我的示例,显示了两种调用方式(print_and_run1 cmd_array
和 print_and_run2 "${cmd_array[@]}"
):
#!/usr/bin/env bash
# Print and run the passed-in command, which is passed in as an
# array **by reference**.
# See here for a full explanation:
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run1 cmd_array
print_and_run1() {
local -n array_reference=""
local PS4='Running cmd: '
local -
set -o xtrace
# Call the cmd
"${array_reference[@]}"
}
# Print and run the passed-in command, which is passed in as members
# of an array **by value**.
# See here for a full explanation:
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run2 "${cmd_array[@]}"
print_and_run2() {
local PS4='Running cmd: '
local -
set -o xtrace
# Call the cmd
"$@"
}
cmd_array=(ls -1 "$HOME/temp/some folder with spaces")
print_and_run1 cmd_array
echo ""
print_and_run2 "${cmd_array[@]}"
echo ""
示例 运行 并输出:
eRCaGuy_hello_world/bash$ ./print_and_run.sh
Running cmd: ls -1 '/home/gabriel/temp/some folder with spaces'
file1.txt
file2.txt
Running cmd: ls -1 '/home/gabriel/temp/some folder with spaces'
file1.txt
file2.txt
在 Bash 脚本中,我经常发现这种模式很有用,我首先打印我将要执行的命令,然后执行命令:
echo 'Running this cmd: ls -1 "$HOME/temp/some folder with spaces'
ls -1 "$HOME/temp/some folder with spaces"
echo 'Running this cmd: df -h'
df -h
# etc.
注意 echo
命令中的 单引号 以防止那里的变量扩展!我的想法是我想打印我正在 运行ning 的 cmd, 完全按照我输入的方式打印 运行 命令 ,然后 运行它!
如何将其封装到一个函数中?
将命令包装到标准 bash 数组中,然后打印并调用它,像这样,sort-of 有效:
# Print and run the passed-in command
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run_cmd cmd_array
# See:
# 1. My answer on how to pass regular "indexed" and associative arrays by reference:
# and
# 1. My answer on how to pass associative arrays:
print_and_run_cmd() {
local -n array_reference=""
echo "Running cmd: ${cmd_array[@]}"
# run the command by calling all elements of the command array at once
${cmd_array[@]}
}
对于像这样的简单命令,它工作正常:
用法:
cmd_array=(ls -a -l -F /)
print_and_run_cmd cmd_array
输出:
Running cmd: ls -a -l -F /
(all output of that cmd is here)
但是对于更复杂的命令,它被破坏了!:
用法:
cmd_array=(ls -1 "$HOME/temp/some folder with spaces")
print_and_run_cmd cmd_array
期望的输出:
Running cmd: ls -1 "$HOME/temp/some folder with spaces"
(all output of that command should be here)
实际输出:
Running cmd: ls -1 /home/gabriel/temp/some folder with spaces
ls: cannot access '/home/gabriel/temp/some': No such file or directory
ls: cannot access 'folder': No such file or directory
ls: cannot access 'with': No such file or directory
ls: cannot access 'spaces': No such file or directory
第一个问题,如您所见,$HOME
在 Running cmd:
行中得到了扩展,而这是不应该的,并且该路径参数周围的双引号已被删除, 第二个问题 是该命令实际上并不 运行.
如何解决这 2 个问题?
参考文献:
- 我的 bash 演示程序,我有这个
print_and_run_cmd
函数:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/argument_parsing__3_advanced__gen_prog_template.sh - 我首先记录了如何通过引用传递 bash 数组,就像我在该函数中所做的那样:
- Passing arrays as parameters in bash
- How to pass an associative array as argument to a function in Bash?
后续问题:
- Bash: how to print and run a cmd array which has the pipe operator, |, in it
检查您的脚本 shellcheck:
Line 2:
local -n array_reference=""
^-- SC2034 (warning): array_reference appears unused. Verify use (or export if used externally).
Line 3:
echo "Running cmd: ${cmd_array[@]}"
^-- SC2145 (error): Argument mixes string and array. Use * or separate argument.
^-- SC2154 (warning): cmd_array is referenced but not assigned.
Line 5:
${cmd_array[@]}
^-- SC2068 (error): Double quote array expansions to avoid re-splitting elements.
您可能想研究一下 https://github.com/koalaman/shellcheck/wiki/SC2068。我们修复了所有错误并得到:
print_and_run_cmd() {
local -n array_reference=""
echo "Running cmd: ${array_reference[*]}"
# run the command by calling all elements of the command array at once
"${array_reference[@]}"
}
对我来说,在这种情况下通过引用传递数组很奇怪。我会传递实际值。我经常这样做:
prun() {
# in the style of set -x
# print to stderr, so output can be captured
echo "+ $*" >&2
# or echo "+ ${*@Q}" >&2
# or echo "+$(printf " %q" "$@")" >&2
# or echo "+$(/bin/printf " %q" "$@")" >&2
"$@"
}
prun "${cmd_array[@]}"
How do I fix these 2 problems?
将 linters、格式化程序和静态分析工具(例如 shellcheck)合并到您的工作流程中,并检查它们指出的问题。
并引用变量扩展。这是 "${array[@]}"
.
您可以使用 DEBUG 陷阱实现您想要的:
#!/bin/bash
set -T
trap 'test "$FUNCNAME" = print_and_run_cmd || trap_saved_command="${BASH_COMMAND}"' DEBUG
print_and_run_cmd(){
echo "Running this cmd: ${trap_saved_command#* }"
"$@"
}
outer(){
print_and_run_cmd ls -1 "$HOME/temp/some folder with spaces"
}
outer
# output ->
# Running this cmd: ls -1 "$HOME/temp/some folder with spaces"
# ...
这似乎也有效:
print_and_run_cmd() {
echo "Running cmd: "
eval "$cmd"
}
cmd='ls -1 "$HOME/temp/some folder with spaces"'
print_and_run_cmd "$cmd"
输出:
Running cmd: ls -1 "$HOME/temp/some folder with spaces"
(result of running the cmd is here)
但现在的问题是,如果我也想打印一个 expanded 版本的命令,以验证该部分是否正常工作,我不能,或者至少,不知道怎么办。
如果您有 Bash 4.4 或更高版本,此函数可能会满足您的要求:
function print_and_run_cmd
{
local PS4='Running cmd: '
local -
set -o xtrace
"$@"
}
例如,运行
print_and_run_cmd echo 'Hello World!'
产出
Running cmd: echo 'Hello World!'
Hello World!
-
当
时自动恢复local PS4='Running cmd: '
为 shell 打印的命令设置前缀。默认值为+
。本地化是指函数returns.PS4
之前的值
中添加了对local -
导致对 shell 选项的任何更改在函数 returns 时自动还原。特别是,当函数 returns 时,它会导致下一行的set -o xtrace
自动撤消。在 Bash 4.4.local -
的支持来自
man bash
,在local [option] [name[=value] ... | - ]
部分下(强调已添加):If name is
-
, the set of shell options is made local to the function in which local is invoked: shell options changed using theset
builtin inside the function are restored to their original values when the function returns.set -o xtrace
(相当于set -x
)导致shell打印命令,前面是PS4
的扩展值,之前运行 他们。参见
help set
。
xtrace
选项打开时,我很喜欢man bash
和 help set
.
不过,我想更改格式并提供更多示例,以表明命令中确实发生了变量扩展。我还想提供一个通过引用的版本,一个不通过引用的版本,这样你就可以选择你最喜欢的调用方式。
这是我的示例,显示了两种调用方式(print_and_run1 cmd_array
和 print_and_run2 "${cmd_array[@]}"
):
#!/usr/bin/env bash
# Print and run the passed-in command, which is passed in as an
# array **by reference**.
# See here for a full explanation:
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run1 cmd_array
print_and_run1() {
local -n array_reference=""
local PS4='Running cmd: '
local -
set -o xtrace
# Call the cmd
"${array_reference[@]}"
}
# Print and run the passed-in command, which is passed in as members
# of an array **by value**.
# See here for a full explanation:
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run2 "${cmd_array[@]}"
print_and_run2() {
local PS4='Running cmd: '
local -
set -o xtrace
# Call the cmd
"$@"
}
cmd_array=(ls -1 "$HOME/temp/some folder with spaces")
print_and_run1 cmd_array
echo ""
print_and_run2 "${cmd_array[@]}"
echo ""
示例 运行 并输出:
eRCaGuy_hello_world/bash$ ./print_and_run.sh
Running cmd: ls -1 '/home/gabriel/temp/some folder with spaces'
file1.txt
file2.txt
Running cmd: ls -1 '/home/gabriel/temp/some folder with spaces'
file1.txt
file2.txt