Bash 如何在脚本中使用getopts,将单独目录中文件的行附加到新文件中?

Bash 如何在脚本中使用getopts,将单独目录中文件的行附加到新文件中?,bash,getopts,Bash,Getopts,我试图编写一个bash脚本,它接收一个目录,读取目录中的每个文件,然后将该目录中每个文件的第一行附加到一个新文件。当我硬编码脚本中的变量时,效果很好 这项工作: #!/bin/bash rm /local/SomePath/multigene.firstline.btab touch /local/SomePath/multigene.firstline.btab btabdir=/local/SomePath/test/* outfile=/local/SomePath/multigene

我试图编写一个bash脚本,它接收一个目录,读取目录中的每个文件,然后将该目录中每个文件的第一行附加到一个新文件。当我硬编码脚本中的变量时,效果很好

这项工作:

#!/bin/bash

rm /local/SomePath/multigene.firstline.btab
touch /local/SomePath/multigene.firstline.btab

btabdir=/local/SomePath/test/*
outfile=/local/SomePath/multigene.firstline.btab

for f in $btabdir
do
    head -1 $f >> $outfile
done
这不起作用:

#!/bin/bash

while getopts ":d:o:" opt; do
  case ${opt} in
    d) btabdir=$OPTARG;;
    o) outfile=$OPTARG;;
  esac
done

rm $outfile
touch $outfile

for f in $btabdir
do
    head -1 $f >> $outfile
done
以下是我如何称呼脚本:

bash /local/SomePath/Scripts/btab.besthits.wBp-q_wBm-r.sh -d /local/SomePath/test/* -o /local/SomePath/out.test/multigene.firstline.btab
下面是我运行它时得到的结果:

rm: missing operand
Try 'rm --help' for more information.
touch: missing file operand
Try 'touch --help' for more information.
/local/SomePath/Scripts/btab.besthits.wBp-q_wBm-r.sh: line 23: $outfile: ambiguous redirect

有什么建议吗?我希望能够使用
getopts
,以便使脚本更通用。谢谢

在编写bash脚本时,您必须特别注意

当您使用glob(
*
此处)调用脚本时,它会被shell展开并拆分为单词。这在脚本执行之前就发生了

例如,如果您执行
cat*.txt
cat将获取目录中的所有.txt文件作为其参数。这与调用
cat afile.txt nextfile.txt
(依此类推)相同。猫永远看不到星号

在您的脚本中,这意味着输入
-d/local/SomePath/test/*
得到扩展的som,类似于
/local/SomePath/test/someFile/local/SomePath/test/someOtherFile/test/someThirdFile
。 随后,
getopts
只获取
-d
之后的第一个文件作为
$btabdir
,而
-o
不会在case开关中处理

我建议您首先引用每个变量,最好使用
“${name}”
样式,并且只使用带引号的输入调用脚本。 它也可以在目录路径中发送,测试它是否是一个目录(
test-d
),并在“${btabdir}”/*

中为f将for循环更改为
,这同样有效:

head -n1 -q /local/SomePath/test/* >> /local/SomePath/out.test/multigene.firstline.btab

我认为正确的答案是“不要那样做。”:-)

当前脚本无法工作的原因可能是,通配符是由交互式shell扩展的,而不是由脚本扩展的。尝试在行首使用
echo
运行命令,以了解实际发生的情况。一旦
getopts
看到glob中第二个匹配的文件,它就会停止处理选项,因此
-o
永远不会被读取,
$outfile
保持未设置状态。由于您没有在
rm$outfile
中引用变量,这就好像您在运行
rm
而没有选项一样。测试shell中
rm
单独与
rm”“
之间的差异

另外,如果文件名中有空格,您的
for
循环会发生什么情况?既然有了bash,就有了数组。而数组更适合处理文件列表

或许可以使用类似的方式:

#!/bin/bash

# initialize an array
files=()

while getopts :d:o: opt; do
  case "$opt" in
    d)
      if [[ ! -d "$OPTARG" ]]; then
        printf 'ERROR: not a directory: %s\n' "$OPTARG" >&2
        exit 65
      fi
      # add to the array
      files+=( "$OPTARG"/* )
      ;;
    o) outfile="$OPTARG" ;;
    *)
      printf 'ERROR: unknown option: %s\n' "$opt" >&2
      exit 64
      ;;
  esac
done

if ! rm -f "$outfile" && touch "$outfile"; then
  printf 'ERROR: cannot create %s\n' "$outfile" >&2
  exit 73
fi

for f in "${files[@]}"; do
  read -r < "$f"
  printf '%s\n' "$REPLY"
done > "$outfile"
#/bin/bash
#初始化数组
文件=()
而getopts:d:o:opt;做
案例“$opt”加入
(d)
如果[!-d“$OPTARG”];然后
printf'错误:不是目录:%s\n'$OPTARG'>&2
65号出口
fi
#添加到数组中
文件+=(“$OPTARG”/*)
;;
o) outfile=“$OPTARG”;;
*)
printf'错误:未知选项:%s\n'$opt'>&2
出口64
;;
以撒
完成
如果!rm-f“$outfile”和触摸“$outfile”;然后
printf'错误:无法创建%s\n'$outfile'>&2
73号出口
fi
对于“${files[@]}”中的f;做
阅读-r<“$f”
printf“%s\n”$REPLY
完成>“$outfile”
以下是这些变化的一些亮点

  • 当然,我们使用的是数组。数组
    ${files[@]}
    将为每条记录包含一个文件,而不依赖于空格,因此通过正确引用,您将避免文件名中的特殊字符问题
  • 我们测试更多的错误条件,并实际显示错误,如果我们看到它们,就会退出。(退出值为。)
  • 我们使用
    read
    和单个重定向到
    $outfile
    ,而不是使用
    head
    。这会将多个fork保存到外部程序,并将多个
    fopen()
    调用保存到输出文件

请注意,
-d
的参数应该是目录,而不是glob。您可以多次指定选项。将同时添加多个
-d
选项,但只使用最后一个
-o
选项。

是的,这可能是最简单的方法,但不是bash。