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

mik*_*ike 4 ls grep bash cygwin for

我想保留所有文件.bat

我试过

for f in $(ls | egrep -v .bat); do echo $f; done
Run Code Online (Sandbox Code Playgroud)

for f in $(eval ls | egrep -v .bat); do echo $f; done
Run Code Online (Sandbox Code Playgroud)

但是这两种方法产生相同的结果,因为它们打印所有内容。而ls | egrep -v .bateval 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*
Run Code Online (Sandbox Code Playgroud)

命令正在工作,但不在 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
Run Code Online (Sandbox Code Playgroud)

调试 $ 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
Run Code Online (Sandbox Code Playgroud)

Sté*_*las 10

您的代码中有一些错误:

使用$(...)不带引号的命令替换 ( ) 而不设置$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
Run Code Online (Sandbox Code Playgroud)

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


Val*_*ami 9

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

通过第一组extglob globstarshopt -s extglob globstar然后

for f in !(*.bat)
  do
   printf '%s\n' "$f"
  done
Run Code Online (Sandbox Code Playgroud)

使用 find

find . -type f ! -name '*.bat' 
Run Code Online (Sandbox Code Playgroud)

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

  • 你甚至可以完全跳过循环并使用 `printf '%s\n' !(*.bat)`(你可以用 `rm` 做同样的事情) (2认同)