我试图执行这个命令:
#!/bin/sh
cmd="ls *.txt | sed s/.txt//g"
for i in `$cmd`
do
echo $i
echo "Hello world"
done
也就是说,在当前工作目录中获取扩展名为.txt的东西,并从文件中删除.txt部分并打印出来。然而,我得到了这个错误,并且ls似乎正在解释"|"以及";sed";以及";s/.txt//g";作为文字文件名:
ls: s/.txt//g: No such file or directory
ls: sed: No such file or directory
ls: |: No such file or directory
如果我将命令传递为for i in ls *.txt | sed s/.txt//g
,这不是问题。我能得到一些建议吗?
几点:
- 如果您希望这是一个bash脚本,而不是sh脚本,则需要更改shebang以调用bash
- BashFAQ#50中描述了评估
$cmd
与使用cmd
内容运行命令行为不同的直接原因 - 另请参阅BashFAQ#48,描述
eval
容易引发安全风险的原因 - shell有自己内置的字符串替换行为;没有理由使用CCD_ 5。请参阅bash黑客关于参数扩展的wiki页面,描述
${var%suffix}
如何在删除suffix
的情况下扩展到$var
的内容 - 在
ls *.txt
中,不是ls
评估*.txt
glob:在启动ls
可执行文件之前,shell本身会执行此操作。因此,运行ls
根本没有意义:它是shell的内置功能,用于生成文件名列表,所以您还可以使用该列表
#!/usr/bin/env bash
for i in *.txt; do i=${i%.txt}
echo "$i"
echo "Hello world"
done
试试这个。
#!/bin/sh
for i in $(ls *.txt | sed s/.txt//g)
do
echo $i
echo "Hello world"
done
简单有效。
基本问题是扩展的顺序(请参阅man-bash中的EXPRANSION(:
展开的顺序是:大括号展开、波浪号展开、,参数、变量和算术展开及命令替换(以从左到右的方式完成(、分词和路径名膨胀
换句话说,命令替换已经假设原始行被拆分为各个命令,并且它将无法识别"|"、";",以及其他bash关键字。
如果命令是常量,简单的解决方案是内联它:
for i in `ls *.txt | sed s/.txt//g`
从OP来看,命令似乎必须是可变的。在这种情况下,可以使用"eval"强制将变量重新解析到管道中:
for i in `eval $cmd`
确保阅读所有关于使用";eval";,以及动态构建命令。
次要注释:在sed
中,命令的/.txt//g'假定.txt
是正则表达式。因此,它将把btxt.txt
更改为.txt
。请确保转义"。"从而将其视为文字。
cmd="ls *.txt | sed -e 's/.txt//g'