Bash脚本编写:构建命令并执行

15

我正在尝试编写一个函数,它接收像 f hello there my friend 这样的参数,并使用 find 在目录中搜索任何一个字符串的所有出现,因此它将是 find | grep 'hello\|there\|my\|friend'。 我对shell脚本不太熟悉,但我的代码如下:

function f { 
  cmd="find | grep '"
  for var in "$@"
  do 
    cmd="$cmd$var\\|"
  done
  cmd="${cmd%\\|}'"
  echo "$cmd"
  $cmd 
}

当我执行该命令时,我会得到这个结果:

# f hello there my friend
find | grep 'hello\|there\|my\|friend'
find: `|': No such file or directory
find: `grep': No such file or directory
find: `\'hello\\|there\\|my\\|friend\'': No such file or directory
为什么它不起作用,我该如何使其起作用?我想这可能与字符串没有被转换为命令有关,但我对shell脚本的工作原理了解不够,无法找出问题所在。

6
好的,以下是翻译的结果:Bash FAQ 050: 如何比较两个文件是否相等?有几种方法可以比较两个文件是否相等。一种简单但不太可靠的方法是使用 diff 命令:if diff file1 file2 >/dev/null ; then echo "file1 and file2 are identical" fidiff 命令将会输出不同之处。如果没有输出,说明两个文件相同。由于只需要判断文件是否相同,我们将其重定向到 /dev/null 中以丢弃所有输出。还有一个更好的方法是使用 cksum 命令:if [ "$(cksum file1)" = "$(cksum file2)" ] ; then echo "file1 and file2 are identical" ficksum 命令会生成一个校验和并输出到 stdout。通过比较两个文件的校验和能够确定它们是否相同。注意,这种方法不是百分之百可靠的,尤其是当两个文件大小相同时。另外,您也可以使用 md5sum 或 sha1sum 等工具来获取文件的摘要,并比较它们是否相同。 - Biffen
等一下...你想在“文件名”还是“文件内容”中搜索这些字符串? - gniourf_gniourf
@gniourf_gniourf 文件名。使用 grep -r 命令搜索内容。 - ewok
好的,关于文件名:你是想要完整路径还是只需要文件名?因为你的方法会包括所有与给定目录名称匹配的文件。 - gniourf_gniourf
5个回答

11

看起来您的命令语法是正确的。要在Bash脚本中运行该命令并捕获结果,请使用以下语法:

cmd_string="ls"
result=$($cmd_string)
echo $result

4

与其将 find 的输出通过 grep 进行管道传输,不如充分利用 find 的全部功能。你需要构建一个包含选项的 数组

-false -o -name '*string1*' ... -o -name '*stringn*'

将参数传递给find(其中string1 ... stringn是作为参数传递的字符串):

f() {
   local i args=( -false )
   for i; do
      args+=( -o -name "*$i*" )
   done
   find "${args[@]}"
}

我们使用 -false 作为初始化器,这样构建选项数组就很简单;这也有一个好处(或缺点,取决于您的观点),即如果没有给出选项,则 find 就会提前退出而不递归列出目录的所有内容。
使用 grep 可以使用正则表达式具有更强大的匹配能力;在这里,我们使用 find-name 选项,因此只能使用基本通配符:*?[...]。如果您的 find 支持 -regex 选项(GNU find 支持),并且如果您确实需要正则表达式,则可以轻松修改上一个函数。
另一个可能性是使用 Bash 的扩展通配符:
f() (
   IFS='|' eval 'glob="$*"'
   shopt -s globstar extglob nullglob
   IFS=
   printf '%s\n' **/*@($glob)*
)

这里有几点需要注意:
  • 整个函数包含在子shell中——这不是笔误。这是为了简化一些事情:不需要使用本地变量,也不需要保存shell选项以在函数结束时恢复它们。
  • 第一行使用了邪恶的eval,但是以安全的方式使用:它实际上是一种用来使用位置参数的第一个字符(这里是管道字符)连接元素的惯用方法。
  • 我们需要将IFS设置为空字符串,以避免在glob ** / @($ glob) *中进行单词拆分。
  • glob ** / @($ glob) *使用了globstarextglob @($ glob)(不带引号,这不是笔误)。 参见参考手册中的模式匹配部分。
此函数使用Bash的扩展glob,与正则表达式不同(并且没有那么强大),但对于大多数情况而言应该足够了。

3

我来到这里是因为它是当我想构建一个带有空格参数的命令行时,唯一的谷歌搜索结果。

解决方案是使用bash数组:

#! /bin/bash
# using /bin/sh here might work if sh is broken or mapped to bash but this is not compatible with a real sh.
MY_ARGS=( "--HI" "--there" "--With spaces" )
if [ "X$OPTIONAL" != "X" ] ; then MY_ARGS+=( "$OPTIONAL" );fi
MY_ARGS+=( "$@" )

my_command "${MY_ARGS[@]}"

来源: http://mywiki.wooledge.org/BashFAQ/050

这是一个关于Bash的问题列表,如果你想了解更多关于Bash的知识,可以参考这个列表。


3
不要将整个命令放在字符串中,只需构建grep的参数。
f () { 
  local grep_arg=''
  delim=''
  for var in "$@"; do
      grep_arg+="$delim$var"
      delim='\|'
  done
  echo "find | grep '$grep_arg'"
  find | grep "$grep_arg"
}

每次运行时,这会将内容附加到grep_arg(即grep_arg的值持续存在)。为什么会发生这种情况? - ewok
抱歉,在shell中变量默认是全局的。我会进行更新。 - chepner

1
最通用的方法:您可以将符号(如“|”(管道)和其他变量)输入到程序中,并从标准输出中获取结果。
function runcmd(){
  eval "$1"
}

result=$(runcmd "Any command ${here}")

1
在你回答的开头加上一个使用管道的例子可能会很有趣? - YLR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接