for 循环中的命令替换不起作用

for 循环中的命令替换不起作用

我想保留所有文件不是结尾为.bat

我试过

for f in $(ls | egrep -v .bat); do echo $f; done

for f in $(eval ls | egrep -v .bat); do echo $f; done

但这两种方法都会产生相同的结果,因为它们会打印所有内容。而如果脱离循环使用, ls | egrep -v .batandeval ls | egrep -v .bat本身就可以工作for

编辑

有趣的是,如果我省略该-v标志,循环就会执行它应该执行的操作并列出所有以.bat.


请随意编辑问题标题,因为我不确定问题是什么。

我在用着GNU bash, version 4.1.10(4)-release (i686-pc-cygwin).


例子

$ ls -l | egrep -v ".bat"
total 60K
-rwx------+ 1 SYSTEM SYSTEM 5.3K Jun  6 20:31 fsc*
-rwx------+ 1 SYSTEM SYSTEM 5.3K Jun  6 20:31 scala*
-rwx------+ 1 SYSTEM SYSTEM 5.3K Jun  6 20:31 scalac*
-rwx------+ 1 SYSTEM SYSTEM 5.3K Jun  6 20:31 scaladoc*
-rwx------+ 1 SYSTEM SYSTEM 5.3K Jun  6 20:31 scalap*

命令正在运行,但不在 for 循环中。

$ for f in $(ls | egrep -v .bat); do echo $f; done
fsc
fsc.bat
scala
scala.bat
scalac
scalac.bat
scaladoc
scaladoc.bat
scalap
scalap.bat
scalac
scalac.bat
scaladoc
scaladoc.bat
scalap
scalap.bat

调试 $ set -x

mike@pc /cygdrive/c/Program Files (x86)/scala/bin
$ for f in $(ls | egrep -v .bat); do echo $f; done
++ ls -hF --color=tty
++ egrep --color=auto -v .bat
+ for f in '$(ls | egrep -v .bat)'
+ echo fsc
fsc
+ for f in '$(ls | egrep -v .bat)'
+ echo fsc.bat
fsc.bat
// and so on

答案1

您的代码中有一些错误:

使用不带引号的命令替换 ( $(...)) 而不进行设置$IFS

split+glob 运算符将扩展不加引号。默认是按空格、制表符和换行符分割。在这里,您只想在换行符上拆分,因此需要将 IFS 设置为该值,否则这意味着如果文件名包含空格或制表符,则无法正常工作

使用不带引号的命令替换,不带set -f.

split+glob 运算符将扩展不加引号。在这里,您不需要通配符,即通配符的扩展,例如扩展scala*到匹配文件列表中。当您不希望 shell 进行通配时,您必须使用以下命令禁用它set -f

ls别名为ls -F

由于您已ls使用别名,上述问题变得更加严重ls -F。它添加/到目录和*可执行文件。因此,通常,因为scala是可执行文件,ls -F并且scala*作为一种通配模式,它会扩展到以它开头的所有文件名,scala这解释了为什么它似乎egrep -v没有过滤掉文件。

假设文件名不包含换行符

换行符与文件名中的任何字符一样有效。所以ls解析通常不起作用的输出。例如,ls包含ab文件的目录中的输出与包含名为 的文件的目录中的输出相同a\nb

上面egrep将过滤文件名的行,而不是文件名

使用egrep代替grep -E

egrep已弃用。grep -E是标准等效值。

不转义.正则表达式运算符。

上面,您曾经egrep启用过扩展正则表达式,但不使用任何扩展 RE 特定运算符。您使用的唯一 RE 运算符是.匹配任何字符,虽然看起来这不是您想要的。所以你可能也用过grep -F这里。或者使用grep -v '\.bat'.

不将正则表达式锚定在行尾

egrep .bat将匹配包含后跟 的任何字符的任何行bat,因此该正则表达式表示bat不包含在第一个位置的任何内容。本来应该是的grep -v '\.bat$'

$f不加引号

不加引号的扩展是 split+glob 运算符。在那里,您两者都不想要,因此$f应该用引号引起来 ( "$f")。

使用echo

echo扩展其参数中的 ANSI C 转义序列和/或-n根据-e实现echo(和/或环境)特殊对待字符串。

改用printf

所以更好的解决方案:

for f in *; do
  case $f in
    (*.bat);;
    (*) printf '%s\n' "$f"
  esac
done

但如果当前目录中没有非隐藏文件,仍然会输出*.您可以通过zsh更改**(N)bash通过运行来解决该问题shopt -s nullglob

答案2

您不应该使用ls这种方式解析文件。

首先设置 extglob globstarshopt -s extglob globstar然后

for f in !(*.bat)
  do
   printf '%s\n' "$f"
  done

使用find

find . -type f ! -name '*.bat' 

使用否定运算符可以更安全地处理文件。

答案3

你的 for 循环本质上没有任何问题:

$ for f in $(ls | egrep -v .bat); do echo $f; done

这是我对上述命令的输出:

$ for f in $(ls | egrep -v .bat); do echo $f; done
fsc
scala
scalac
scaladoc
scalap

可能的修复

一个建议是引用 的参数egrep,如下所示:

$ for f in $(ls | egrep -v '.bat'); do echo $f; done

在 Bash 中编写脚本时,还可以查看 Bash Pitfalls wiki,了解其他潜在问题。特别是这听起来像最合理的理由为什么你会遇到这个问题。检查返回的任何文件是否包含空格。

调试

另一件要尝试的事情是在运行 bash 命令之前启用它的详细调试,以便您可以看到幕后发生的情况。

这可以实现调试:

$ set -x

然后运行你的命令:

$ for f in $(ls | egrep -v .bat); do echo $f; done
++ ls --color=auto
++ egrep --color=auto -v .bat
+ for f in '$(ls | egrep -v .bat)'
+ echo fsc
fsc
+ for f in '$(ls | egrep -v .bat)'
+ echo scala
scala
+ for f in '$(ls | egrep -v .bat)'
+ echo scalac
scalac
+ for f in '$(ls | egrep -v .bat)'
+ echo scaladoc
scaladoc
+ for f in '$(ls | egrep -v .bat)'
+ echo scalap
scalap

然后禁用它:

$ set +x

CYGWIN 有问题吗?

鉴于这些示例在许多本机 Linux 系统上运行良好,问题很可能根源于与 Cygwin 和/或其特定版本的bash和 有关egrep。我会特别注意 Bash 中的字段分隔符,看看 Windows ( ) 与 Unix ( )$IFS上的各种行分隔符之间是否存在问题。0x0d,0x0a0x0a

我没有安装 Cygwin,所以我没有办法证明这个假设。

相关内容