“eval”和“source /dev/stdin”有什么区别?

“eval”和“source /dev/stdin”有什么区别?

在以下选项之间...

  1. eval

    comd="ls"
    eval "$comd"
    
  2. source /dev/stdin

    printf "ls" | source /dev/stdin
    
  3. source /dev/stdin( ){ }

    ( printf "ls" ) | source /dev/stdin
    { printf "ls"; } | source /dev/stdin
    

    (当我们运行printfin时{ },除了不使用subshel​​l还有什么好处吗?)

    • 它们之间有什么区别?

    • 哪个是首选?

    • 哪种是运行命令的首选方式?()或者{}

答案1

各种方式有什么区别?

man bash

eval [arg ...]
              The  args  are read and concatenated together into a single com‐
              mand.  This command is then read and executed by the shell,  and
              its  exit status is returned as the value of eval.  If there are
              no args, or only null arguments, eval returns 0.

source filename [arguments]
              Read and execute commands from filename  in  the  current  shell
              environment  and return the exit status of the last command exe‐
              cuted from filename.  If filename does not contain a slash, file
              names  in  PATH  are used to find the directory containing file‐
              name.  The file searched for in PATH  need  not  be  executable.
              When  bash  is  not  in  posix  mode,  the  current directory is
              searched if no file is found in PATH.  If the sourcepath  option
              to  the  shopt  builtin  command  is turned off, the PATH is not
              searched.  If any arguments are supplied, they become the  posi‐
              tional  parameters  when  filename  is  executed.  Otherwise the
              positional parameters are unchanged.  The return status  is  the
              status  of  the  last  command exited within the script (0 if no
              commands are executed), and false if filename is  not  found  or
              cannot be read.

两种方式没有区别。

只有一个注释:eval连接其所有参数,然后将其作为单个命令运行。source读取文件的内容并执行它们。eval只能从其参数构建命令,而不能stdin。所以你不能这样做:

printf "ls" | eval

哪个更优选?

eval您的示例提供了相同的结果,但和的目的source不同。source通常用于为其他脚本提供库,而eval仅用于评估命令。eval如果可能的话应该避免,因为不能保证eval'ed 字符串是干净的;必须使用subshell.

()如果我们在或中运行一些命令{},哪个更优选?

当命令序列在花括号内运行时{ },所有命令都在当前外壳, 代替子外壳(如果您在括号内运行,就会出现这种情况(请参阅 bash参考))。

使用subshell ( )会占用更多资源,但不影响当前环境。 using{ }会运行当前shell中的所有命令,因此环境会受到影响。选择哪个取决于您的目的。

答案2

主要区别在于第二种和第三种形式使用管道,这将强制 bash 在子 shell 中运行“source”命令(除非设置了 Lastpipe,仅在 bash 4.2+ 中可用),这将使其几乎相当于:

printf "ls" | bash

后果是代码设置的任何环境变量都将丢失,因此这将无法按预期工作:

printf "abc=2" | source /dev/stdin

要在当前 shell 中运行命令,可以使用进程替换:

source <(printf "abc=2")

您可以像往常一样使用分号将更多命令放在括号内。

如果您以这种方式消除管道,我相信使用“eval”和“source”之间没有区别。您应该更喜欢在您的特定情况下使用更简单的一种:

  • 如果您已经有要在变量中运行的命令,请使用“eval”
  • 如果您将它们放在文件中或从外部命令获取它们,请使用“source”

答案3

作为对已经给出的答案的补充:

相当于source...

comd="ls"
eval "$comd"

... 是 ...

source <(printf ls)

的情况下ls没有显着差异。

但如果命令的目的是影响你现在的环境(您在使用时通常想要的source)这个变体会做到这一点(就像您的第一个解决方案一样eval),而您的第二种方法只会影响子shell的环境,该子shell在执行代码行后将不可用。

答案4

有一个重要的区别尚未做出!

❯ echo return | . /dev/stdin

❯ echo return | eval "$(cat -)"
sh: return: can only `return' from a function or sourced script

也就是说,return仅在采购脚本时才有效。

根据脚本的内容和意图,您选择哪个可能并不重要,但我认为这种差异值得指出。


在上面我用管道来eval代替 an 的eval return原因有两个:

  1. 只是为了证明可以通过管道进入eval.

  2. 为了保持一致性,因此source和都eval可以在子外壳(管道)内运行。

    例如,如果我们尝试这样的事情:

    ❯ echo a=1 | . /dev/stdin; echo $a
    ❯ echo a=1 | eval "$(cat -)"; echo $a
    

    我们会发现1它没有被打印,因为它没有在我们当前的环境中进行评估。我们必须执行以下操作才能1打印:

    ❯ echo a=1 | { . /dev/stdin; echo $a; }
    ❯ echo a=1 | { eval "$(cat -)"; echo $a; }
    

相关内容