AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

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

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / computer / 问题 / 1839405
Accepted
pmor
pmor
Asked: 2024-04-17 21:30:39 +0800 CST2024-04-17 21:30:39 +0800 CST 2024-04-17 21:30:39 +0800 CST

如何从字符串变量运行包含字符串变量的命令?

  • 772

我正在尝试运行包含字符串变量中的字符串变量的命令:

$ X="bash -c 'echo OK'" ; $X
OK': -c: line 1: unexpected EOF while looking for matching `''

为什么不起作用?如何正确修复/做它?

请注意,我需要将命令运行为$X.

请注意,我需要echo OK通过运行bash -c。

bash
  • 2 2 个回答
  • 34 Views

2 个回答

  • Voted
  1. Best Answer
    fraxflax
    2024-04-17T21:52:31+08:002024-04-17T21:52:31+08:00

    因为它尝试运行名为bash -c 'echo OK'

    eval $X会工作

    • 2
  2. harrymc
    2024-04-17T23:41:37+08:002024-04-17T23:41:37+08:00

    帖子 如何运行存储在变量中的命令?包含ilkkachu 的 很长的 答案 ,它以非常彻底的方式讨论了这个问题。

    我将在这里引用这个优秀的答案。

    这已经在 unix.SE 上的许多问题中进行了讨论,我将尝试收集我可以在这里提出的所有问题。下边是

    • 各种尝试失败的原因和方式的描述,
    • 一种使用函数(对于固定命令)正确执行此操作的方法,或者
    • 使用 shell 数组 (Bash/ksh/zsh) 或$@伪数组 (POSIX sh),如果您只需要改变一些选项,这两者也允许构建命令行片段
    • 以及有关使用eval此操作的注释。

    一些参考资料在最后。

    出于此处的目的,如果仅将命令参数或命令名称存储在变量中,则没有多大关系。在启动命令之前,它们的处理方式类似,此时 shell 仅将第一个单词作为要运行的命令的名称。


    为什么失败

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

    (请注意,有关引号的部分与所有其他编程语言类似:例如,char *s = "foo()"; printf("%s\n", s)不会调用foo()C 中的函数,而只是打印字符串 foo()。这在宏处理器中有所不同,例如 m4、C 预处理器或 Make(在某种程度上) . shell 是一种编程语言,而不是宏处理器。)

    在类 Unix 系统上,shell 负责处理命令行上的引号和变量扩展,将其从单个字符串转换为底层系统调用传递给启动命令的字符串列表。程序本身看不到 shell 处理的引号。例如,如果给出命令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/my和dir"。这里的引号只是数据,所以第二个参数前面有一个,第三个参数后面有一个。该选项有效,但路径处理不正确,因为ls将引号视为文件名的一部分:

    $ $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", and 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")
    
    # expand the array, run the command
    "${mycmd[@]}"
    

    该命令在括号内的书写方式与运行该命令时的书写方式完全相同。shell 所做的处理在两种情况下都是相同的,只是在一种情况下它只保存结果字符串列表,而不是使用它来运行程序。

    不过,稍后扩展数组的语法有点糟糕,而且它周围的引号很重要。

    数组还允许您逐个构建命令行。例如:

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

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

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

    (somecommand这里是通用占位符名称,不是任何真正的命令。)

    数组的缺点是它们不是标准功能,因此普通的 POSIX shell(例如Debian/Ubuntu 中的dash默认设置/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
    
    somecommand "$@"
    

    当然,"$@"通常充满了脚本本身的参数,因此您必须在重新使用之前将它们保存在某个地方 "$@"。

    要有条件地传递单个参数,您还可以使用替代值扩展${var:+word}和一些仔细的引用。-f 在这里,仅当文件名非空时,我们才包含和 文件名:

    file="foo bar"
    somecommand ${file:+-f "$file"}
    

    使用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";
    

    但是,如果用户提供包含单引号的输入,他们可以突破引号并运行任意命令!例如,使用输入'$(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 脚本会因为空格或其他特殊字符而卡住?,其中讨论了许多与引用和空格相关的问题,包括存储命令。
    • 转义变量以用作另一个脚本的内容
    • https://unix.stackexchange.com/q/562870/170373
    • 1

相关问题

  • 在非 root 用户上用 bash 替换 zsh

  • 在 macOS High Sierra 的终端中设置环境变量时遇到问题

  • 对于 cp 或 mv,是否有等同于 cd - 的东西?

  • Notify-发送窗口下出现的通知

  • 如何从 WSL 打开 office 文件

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何减少“vmmem”进程的消耗?

    • 11 个回答
  • Marko Smith

    从 Microsoft Stream 下载视频

    • 4 个回答
  • Marko Smith

    Google Chrome DevTools 无法解析 SourceMap:chrome-extension

    • 6 个回答
  • Marko Smith

    Windows 照片查看器因为内存不足而无法运行?

    • 5 个回答
  • Marko Smith

    支持结束后如何激活 WindowsXP?

    • 6 个回答
  • Marko Smith

    远程桌面间歇性冻结

    • 7 个回答
  • Marko Smith

    子网掩码 /32 是什么意思?

    • 6 个回答
  • Marko Smith

    鼠标指针在 Windows 中按下的箭头键上移动?

    • 1 个回答
  • Marko Smith

    VirtualBox 无法以 VERR_NEM_VM_CREATE_FAILED 启动

    • 8 个回答
  • Marko Smith

    应用程序不会出现在 MacBook 的摄像头和麦克风隐私设置中

    • 5 个回答
  • Martin Hope
    Vickel Firefox 不再允许粘贴到 WhatsApp 网页中? 2023-08-18 05:04:35 +0800 CST
  • Martin Hope
    Saaru Lindestøkke 为什么使用 Python 的 tar 库时 tar.xz 文件比 macOS tar 小 15 倍? 2021-03-14 09:37:48 +0800 CST
  • Martin Hope
    CiaranWelsh 如何减少“vmmem”进程的消耗? 2020-06-10 02:06:58 +0800 CST
  • Martin Hope
    Jim Windows 10 搜索未加载,显示空白窗口 2020-02-06 03:28:26 +0800 CST
  • Martin Hope
    andre_ss6 远程桌面间歇性冻结 2019-09-11 12:56:40 +0800 CST
  • Martin Hope
    Riley Carney 为什么在 URL 后面加一个点会删除登录信息? 2019-08-06 10:59:24 +0800 CST
  • Martin Hope
    zdimension 鼠标指针在 Windows 中按下的箭头键上移动? 2019-08-04 06:39:57 +0800 CST
  • Martin Hope
    jonsca 我所有的 Firefox 附加组件突然被禁用了,我该如何重新启用它们? 2019-05-04 17:58:52 +0800 CST
  • Martin Hope
    MCK 是否可以使用文本创建二维码? 2019-04-02 06:32:14 +0800 CST
  • Martin Hope
    SoniEx2 更改 git init 默认分支名称 2019-04-01 06:16:56 +0800 CST

热门标签

windows-10 linux windows microsoft-excel networking ubuntu worksheet-function bash command-line hard-drive

Explore

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

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve