为什么Linux命令CP在CLI和脚本中的行为不同



我想复制一堆Verilog/systemverilog源,所以我使用带有通配符表达式的CP:

cp <some_dir>/*.{v,sv,svh} .

它有效。但是,当我把它放在一个具有完全相同行的脚本中时,CP命令失败,并显示日志:

cp: cannot stat `../../mytest/spiTest/*.{v,sv,svh}': No such file or directory

这是怎么发生的?

附言:我用bash作为外壳。


这是我的脚本:

#!/bin/bash
rdir=../../mytest/spiTest
f1="$rdir/bench.lst"
f2="$rdir/cphex" #the script to copy rom data
f3="$rdir/make*" #makefile scripts
f4="$rdir/*.hex" #rom files
f5="$rdir/*.{v,sv,svh}" #testbench files
echo 'Copying files...'
cp $f1 $f2 $f3 $f4 .
cp $f5 .

我确实将第一行更改为

#!/bin/bash -vx

然后再次运行这个脚本,我得到:

#!/bin/bash -vx
rdir=../../mytest/spiTest
+ rdir=../../mytest/spiTest
f1="$rdir/bench.lst"
+ f1=../../mytest/spiTest/bench.lst
f2="$rdir/cphex" #the script to copy rom data
+ f2=../../mytest/spiTest/cphex
f3="$rdir/make*" #makefile scripts
+ f3='../../mytest/spiTest/make*'
f4="$rdir/*.hex" #rom files
+ f4='../../mytest/spiTest/*.hex'
f5="$rdir/*.{v,sv,svh}" #testbench files
+ f5='../../mytest/spiTest/*.{v,sv,svh}'
echo 'Copying files...'
+ echo 'Copying files...'
Copying files...
cp $f1 $f2 $f3 $f4 .
+ cp ../../mytest/spiTest/bench.lst ../../mytest/spiTest/cphex ../../mytest/spiTest/makefile ../../mytest/spiTest/makefile.defines ../../mytest/spiTest/rom.hex ../../mytest/spiTest/rom_if.hex .
cp $f5 .
+ cp '../../mytest/spiTest/*.{v,sv,svh}' .
cp: cannot stat `../../mytest/spiTest/*.{v,sv,svh}': No such file or directory

检查脚本的第一行。上面可能写着:

#!/bin/sh

其将外壳从BASH切换到Bourne外壳。使用

#!/bin/bash

相反。

[EDIT]您在扩展时遇到问题。BASH有一定的扩展模式和变量的顺序。这意味着:

f5="$rdir/*.{v,sv,svh}" #testbench files

被引用,因此此时不会进行文件名扩展。只有变量$rdir被展开。当

cp $f5 .

执行时,BASH首先查找要展开的文件名,但没有。然后它展开变量(f5),然后用两个参数调用cp../../mytest/spiTest/*.{v,sv,svh}.。由于cp预计shell已经执行了文件名扩展,因此会出现错误。

要解决这个问题,你必须使用数组:

f5=($rdir/*.{v,sv,svh})

这将替换变量,然后展开文件名,并将所有内容放入数组f5中。然后,您可以使用此数组调用cp,同时保留空白:

cp "${f5[@]}" .

这里的每一个字符都很重要。[@]告诉BASH在这里扩展整个数组。引号上写着:保留空白。{}需要告诉BASH[@]是要展开的变量"name"的一部分。

问题是:替换的顺序。Bash在变量展开之前执行大括号展开。在cp $f5 .行中,bash将执行:

  1. 大括号扩展:n/a
    • 这是关键:变量包含一个大括号表达式,但shell在需要时不会立即看到它
  2. 波浪形展开:n/a
  3. 参数扩展:是--cp ../../mytest/spiTest/*.{v,sv,svh} .
  4. 命令替换:n/a
  5. 算术展开:n/a
  6. 工艺替代:不适用
  7. 分词:不适用
  8. 文件名扩展:是的,bash在该目录中查找以字符串
    .{v,sv,svh}结尾的文件。它没有找到,nullglob没有设置,因此该模式没有从命令中删除
  9. 引号删除:不适用

现在命令被执行,但失败并出现您看到的错误。

https://www.gnu.org/software/bash/manual/bashref.html#Shell-扩展

解决方案:

  1. 使用Aaron的数组思想
  2. (不推荐)强制进行第二轮扩展:eval cp $f5 .

f5="$rdir/*.{v,sv,svh}" #testbench files

可能是错的。首先,避免在行尾注释,它们应该(至少为了可读性)在单独的一行中。然后,避免在变量赋值中使用globbing。因此,删除该行,稍后进行编码(即,用替换旧的cp $f5 .行)

cp "$rdir"/*.{v,sv,svh} .

顺便说一句,我想测试一下"$rdir"确实是一个带有的目录

if [ ! -d "$rdir" ] ; then
echo invalid directory $rdir > /dev/stderr
exit 1
fi

您应该阅读高级Bash脚本指南

最新更新