AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
主页 / unix / 问题 / 444946
Accepted
Tim
Tim
Asked: 2018-05-21 04:46:29 +0800 CST2018-05-21 04:46:29 +0800 CST 2018-05-21 04:46:29 +0800 CST

我们如何运行存储在变量中的命令?

  • 772
$ ls -l /tmp/test/my\ dir/
total 0

我想知道为什么以下运行上述命令的方法失败或成功?

$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0
bash shell
  • 5 5 个回答
  • 196816 Views

5 个回答

  • Voted
  1. Best Answer
    ilkkachu
    2018-05-21T04:58:21+08:002018-05-21T04:58:21+08:00

    这已在 unix.SE 上的许多问题中讨论过,我将尝试收集我能在这里提出的所有问题。下面是对各种尝试失败的原因和方式的描述,以及使用函数(对于固定命令)或 shell 数组(Bash/ksh/zsh)或$@伪数组(POSIX sh)正确执行此操作的方法,以及关于使用eval来执行此操作的说明。一些参考在最后。

    出于此处的目的,它是否只是命令参数或要存储在变量中的命令名称并不重要。在启动命令之前,它们的处理方式类似,此时 shell 仅将第一个单词作为要运行的命令的名称。


    为什么会失败

    您面临这些问题的原因是分词非常简单并且不适合复杂的情况,并且从变量扩展的引号不充当引号,而只是普通字符。

    (请注意,关于引号的部分与其他所有编程语言相似:例如char *s = "foo()"; printf("%s\n", s),不调用foo()C 中的函数,而只是打印字符串foo()。shell 是一种编程语言,而不是宏处理器。)

    请记住,在命令行上处理引号和变量扩展的是 shell,将其从单个字符串转换为最终传递给启动命令的字符串列表。程序本身看不到任何引号。例如,如果给定 command ls -l "foo bar",shell 会将其转换为三个字符串ls, -land foo bar(删除引号),并将它们传递给ls. (即使是命令名也被传递了,虽然不是所有的程序都使用它。)

    问题中提出的案例:

    这里的赋值将单个字符串分配ls -l "/tmp/test/my dir"给abc:

    $ abc='ls -l "/tmp/test/my dir"'
    

    下面,$abc在空格处拆分,并ls获取三个参数-l, "/tmp/test/myand dir"(在第二个前面有一个引号,在第三个后面有另一个引号)。该选项有效,但路径处理不正确:

    $ $abc
    ls: cannot access '"/tmp/test/my': No such file or directory
    ls: cannot access 'dir"': No such file or directory
    

    在这里,扩展被引用,所以它被保留为一个单词。shell 试图找到一个字面上称为 的程序ls -l "/tmp/test/my dir",包括空格和引号。

    $ "$abc"
    bash: ls -l "/tmp/test/my dir": No such file or directory
    

    在这里,$abc被分割了,只有第一个结果词被作为参数-c,所以 Bash 只是ls在当前目录中运行。其他词是 bash 的参数,用于填充$0,$1等。

    $ bash -c $abc
    'my dir'
    

    使用bash -c "$abc", 和eval "$abc",还有一个额外的 shell 处理步骤,它确实使引号起作用,但也会导致所有 shell 扩展被再次处理,因此存在意外运行的风险,例如从用户提供的数据进行命令替换,除非你是引用非常小心。


    更好的方法来做到这一点

    存储命令的两种更好的方法是 a) 使用函数,b) 使用数组变量(或位置参数)。

    使用函数:

    只需在内部声明一个带有命令的函数,然后像运行命令一样运行该函数。函数内命令的扩展仅在命令运行时处理,而不是在定义时处理,您不需要引用各个命令。

    # define it
    myls() {
        ls -l "/tmp/test/my dir"
    }
    
    # run it
    myls
    

    使用数组:

    数组允许创建多个单词变量,其中单个单词包含空格。在这里,各个单词存储为不同的数组元素,"${array[@]}"扩展将每个元素扩展为单独的 shell 单词:

    # define the array
    mycmd=(ls -l "/tmp/test/my dir")
    
    # run the command
    "${mycmd[@]}"
    

    语法有点可怕,但数组还允许您逐段构建命令行。例如:

    mycmd=(ls)               # initial command
    if [ "$want_detail" = 1 ]; then
        mycmd+=(-l)          # optional flag
    fi
    mycmd+=("$targetdir")    # the filename
    
    "${mycmd[@]}"
    

    或保持部分命令行不变并使用数组填充其中的一部分,如选项或文件名:

    options=(-x -v)
    files=(file1 "file name with whitespace")
    target=/somedir
    
    transmutate "${options[@]}" "${files[@]}" "$target"
    

    数组的缺点是它们不是标准功能,因此普通的 POSIX shell(如Debian/Ubuntudash中的默认值/bin/sh)不支持它们(但见下文)。但是,Bash、ksh 和 zsh 可以,因此您的系统很可能有一些支持数组的 shell。

    使用"$@"

    在不支持命名数组的 shell 中,仍然可以使用位置参数(伪数组"$@")来保存命令的参数。

    以下应该是与上一节中的代码位等效的可移植脚本位。该数组被替换"$@"为位置参数列表。设置"$@"是用 完成的set,并且双引号"$@"很重要(这些导致列表的元素被单独引用)。

    首先,简单地存储一个带有参数的命令"$@"并运行它:

    set -- ls -l "/tmp/test/my dir"
    "$@"
    

    有条件地为命令设置部分命令行选项:

    set -- ls
    if [ "$want_detail" = 1 ]; then
        set -- "$@" -l
    fi
    set -- "$@" "$targetdir"
    
    "$@"
    

    仅"$@"用于选项和操作数:

    set -- -x -v
    set -- "$@" file1 "file name with whitespace"
    set -- "$@" /somedir
    
    transmutate "$@"
    

    (当然,"$@"通常会填充脚本本身的参数,因此您必须在重新调整用途之前将它们保存在某个地方"$@"。)


    使用eval(这里要小心!)

    eval接受一个字符串并将其作为命令运行,就像在 shell 命令行中输入它一样。这包括所有的报价和扩展处理,这既有用又危险。

    在简单的情况下,它允许做我们想做的事:

    cmd='ls -l "/tmp/test/my dir"'
    eval "$cmd"
    

    随着eval,引号被处理,所以ls最终只看到两个参数-l和/tmp/test/my dir,就像我们想要的那样。eval也很聪明,可以连接它得到的任何参数,所以eval $cmd在某些情况下也可以工作,但是例如所有的空白运行将被更改为单个空格。最好在此处引用变量,因为这将确保它未被修改为eval.

    但是,在命令字符串中包含用户输入是很危险的eval。例如,这似乎有效:

    read -r filename
    cmd="ls -ld '$filename'"
    eval "$cmd";
    

    但是如果用户给出的输入包含单引号,他们可以跳出引号并运行任意命令!例如,使用 input '$(whatever)'.txt,您的脚本会愉快地运行命令替换。相反,它可能是rm -rf(或更糟)。

    问题在于 的值$filename嵌入在eval运行的命令行中。它之前被扩展过eval,它看到了例如命令ls -l ''$(whatever)'.txt'。您需要对输入进行预处理以确保安全。

    如果我们以另一种方式来做,将文件名保留在变量中,并让eval命令扩展它,它又会更安全:

    read -r filename
    cmd='ls -ld "$filename"'
    eval "$cmd";
    

    请注意,外部引号现在是单引号,因此不会发生内部扩展。因此,eval看到命令ls -l "$filename"并安全地扩展文件名本身。

    但这与仅将命令存储在函数或数组中没有太大区别。对于函数或数组,就没有这样的问题,因为单词一直是分开的,并且没有对filename.

    read -r filename
    cmd=(ls -ld -- "$filename")
    "${cmd[@]}"
    

    几乎唯一使用的原因eval是可变部分涉及无法通过变量(管道、重定向等)引入的 shell 语法元素。但是,您随后需要在命令行上引用/转义需要保护免受额外解析步骤影响的所有其他内容(请参见下面的链接)。无论如何,最好避免在命令中嵌入来自用户的输入eval!


    参考

    • BashGuide中的分词
    • BashFAQ/050 或“我正在尝试将命令放入变量中,但复杂的情况总是失败!”
    • 问题为什么我的 shell 脚本会因空格或其他特殊字符而窒息?,其中讨论了与引用和空格相关的许多问题,包括存储命令。
    • 转义变量以用作另一个脚本的内容
    • 205
  2. Hauke Laging
    2018-05-21T05:20:26+08:002018-05-21T05:20:26+08:00

    运行(重要)命令的最安全方法是eval. 然后,您可以像在命令行上一样编写命令,它的执行就像您刚刚输入它一样。但是你必须引用所有内容。

    简单案例:

    abc='ls -l "/tmp/test/my dir"'
    eval "$abc"
    

    不是那么简单的情况:

    # command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
    abc='awk '\''! a[$0]++ { print "foo: " $0; }'\'' inputfile'
    eval "$abc"
    
    • 15
  3. ingyhere
    2022-09-08T19:13:13+08:002022-09-08T19:13:13+08:00

    如果它需要一个数组来执行,那就把它变成一个数组!

    IFS=' ' read -r -a command_arr <<< "${command}"
    "${command_arr[@]}"
    

    第一行将字符串转换为数组。第二行执行命令。

    这似乎不适用于链式命令,例如使用&&or ;。

    • 1
  4. Wattana Gaming
    2018-05-21T05:02:58+08:002018-05-21T05:02:58+08:00

    第二个引号中断命令。

    当我运行时:

    abc="ls -l '/home/wattana/Desktop'"
    $abc
    

    它给了我一个错误。

    但是当我跑步时

    abc="ls -l /home/wattana/Desktop"
    $abc
    

    完全没有错误

    当时没有办法解决这个问题(对我来说),但您可以通过在目录名称中没有空格来避免错误。

    这个答案 说 eval 命令可以用来解决这个问题,但它对我不起作用:(

    • 0
  5. bloody
    2020-04-14T11:42:20+08:002020-04-14T11:42:20+08:00

    abc运行存储在变量中的任何(平凡/非平凡)命令的另一个技巧是:

    $ history -s $abc
    

    并按UpArrow或Ctrl-p将其带入命令行。与任何其他方法不同,如果需要,您可以在执行之前对其进行编辑。

    此命令会将变量的内容作为新条目附加到 Bash 历史记录中,您可以通过UpArrow.

    • -3

相关问题

  • 从文本文件传递变量的奇怪问题

  • 虽然行读取保持转义空间?

  • 如何将带有〜的路径保存到变量中?

  • `tee` 和 `bash` 进程替换顺序

  • 运行一个非常慢的脚本直到它成功

Sidebar

Stats

  • 问题 199622
  • 回答 264146
  • 最佳答案 132073
  • 用户 66540
  • 热门
  • 回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    ssh 无法协商:“找不到匹配的密码”,正在拒绝 cbc

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    如何卸载内核模块“nvidia-drm”?

    • 13 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Wong Jia Hau ssh-add 返回:“连接代理时出错:没有这样的文件或目录” 2018-08-24 23:28:13 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Bagas Sanjaya 为什么 Linux 使用 LF 作为换行符? 2017-12-20 05:48:21 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve