bash - 临时文件没有被赋予正确的名称。 $BASHPID 正在改变?

标签 bash

我认为这应该是直截了当的。生成一个临时文件并输出它。显然,幕后发生了一些奇怪的事情。

function x {
  cat $2 > /tmp/$BASHPID.$$;
  cat /tmp/$BASHPID.$$;  # < FAILS because $BASHPID has changed???
};
echo a>/tmp/junk;
x $$ /tmp/junk &

但这有效:

function x {
  local tmp=/tmp/$BASHPID.$$;
  cat $2 > $tmp;
  cat $tmp;  # < WORKS!
};
echo a>/tmp/junk;
x $$ /tmp/junk &

$BASHPID 到底是什么?我认为它基本上就像 $$ ,除了如果在子进程中执行,它将获得子进程 PID。执行cat的时候,是不是真的得到了cat的PID?

最佳答案

这是 bash 中 subshel​​l 普遍问题的一个特例。您不需要 $BASHPID 就可以看到它。

bash manual很明显,重定向和赋值是在扩展所有其他词之后扩展的:

When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.

  1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.
  2. The words that are not variable assignments or redirections are expanded (see Shell Expansions). If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments. …

因此,以下情况不足为奇:

$ unset tmp; a=$tmp eval "echo ${tmp:=foo} \$a"
foo foo

这里,${tmp:=foo}a=$tmp 之前展开,结果 a=foo 被传入echo foo $a 的评估环境。

从这里开始,事情变得更加模糊。手册说重定向在赋值之前展开(实际上是第 3 点和第 4 点;我不能让 markdown 合作):

  1. Redirections are performed as described above (see Redirections).
  2. The text after the ‘=’ in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.

但是正如我们在这里看到的,分配似乎首先被扩展:

$ unset tmp; a=$tmp eval <${tmp:=foo} 'echo :$a:'
::
$ unset tmp; <${tmp:=foo} a=$tmp eval 'echo :$a:'
::

虽然手册明确指出一旦 bash 确定命令是外部实用程序:

the shell executes the named program in a separate execution environment (from the next section of the manual.)

并不清楚赋值和重定向中的单词扩展有多少是在命令的执行环境中完成的,有多少是在原始环境中完成的。而且,事实上,这种行为并不容易预测。

通过实验(使用 bash 4.2),我们可以看到:

  1. 分配在父环境中展开。
  2. 重定向在子环境中扩展,如果有的话。
  3. 在内置的情况下,不会创建子项,并且在父项中扩展重定向:
$ unset tmp; a=${tmp:=foo} cat </dev/null; echo tmp=$tmp
tmp=foo
$ unset tmp; >${tmp:=foo} cat </dev/null; echo tmp=$tmp
tmp=
$ unset tmp; >${tmp:=foo} echo </dev/null; echo tmp=$tmp
tmp=foo

尽管恕我直言,分配和重定向的顺序应该更准确地记录,但以上所有内容至少是合理的。但是,以下内容 —— 如果文件不存在,则对 stdin 重定向中的扩展进行两次评估 —— 肯定是一个错误:

$ tmp=0; /bin/echo .. <$((tmp+=2)); echo $tmp
bash: 4: No such file or directory
0

# As expected, with a builtin tmp is altered in the parent environment
$ tmp=0; echo .. <$((tmp+=2)); echo $tmp
bash: 4: No such file or directory
4

因为 $BASHPID 始终是 $BASHPID 实际展开的进程的 pid,所有这些不同的问题也会影响它的值。一般来说,我认为唯一安全的规则是:

  • 不要修改环境或依赖于 $BASHPID 除了:
    • 独立作业,或
    • 命令或参数词中的简单参数扩展。

附带说明一下,为临时文件生成名称的最佳方法是使用 mktemp 实用程序。

关于bash - 临时文件没有被赋予正确的名称。 $BASHPID 正在改变?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24436014/

相关文章:

bash - BASH 变量名称中可以接受哪些 Unicode 符号?

linux - bash 计时器(以毫秒为单位)

bash - 运行 bash 脚本的 docker 入口点获取 "permission denied"

linux - 衡量开发/随机效率

linux - 默认的bash目录是什么目录

c++ - 编写一个 Shell 脚本来运行我的带有输入的程序

OSX 上的 python 和 echo -n

bash - shell 脚本中出现意外标记错误附近的语法错误

linux - EXT4 上的时间戳精度(亚毫秒)

node.js - 在不使用额外的 NPM 包的情况下检查 package.json 是否在 shell 脚本中具有特定名称的脚本