将 bash 变量传递给 grep 命令的文件名

pass bash variable to filename for grep command

提问人:blakem 提问时间:11/2/2023 更新时间:11/2/2023 访问量:74

问:

我有一个文件,其中包含一个文件名前缀列表,我想对这些前缀执行 grep 以 grep 出一组特定的行,这些行包含坐标,用于后续计算均方根偏差,我将在 awk 中使用单行代码执行。我已经验证了我的正则表达式的语法适用于单个文件,但是当我从 for 循环中使用 bash 变量时,grep 将无法识别该变量。我尝试了双引号、转义字符和波浪形大括号的多种排列,但它们都没有产生所需的输出。以下是初始 grep 后的原始数据的样子,并通过管道到第二个 grep 执行以清理 awk 步骤的数据:

grep " [A-Z] " ../glide-dock_SP_8CHM/ligands/3579831839.sdf | grep " 'F\|S\|C\|O\|N "
    2.7118    4.0281   21.0125 S   0  0  0  0  0  0
    4.0921    3.8708   21.4967 O   0  0  0  0  0  0
    1.8208    2.8602   20.9648 O   0  0  0  0  0  0
    1.9954    5.2598   21.8979 N   0  0  1  0  0  0
    2.8079    4.6789   19.3978 N   0  0  0  0  0  0
    2.3264    6.6613   21.7269 C   0  0  0  0  0  0
    0.5679    5.5328   21.8880 C   0  0  0  0  0  0
    3.8805    4.1396   18.5518 C   0  0  0  0  0  0
    1.5591    4.8798   18.6409 C   0  0  0  0  0  0
    0.9197    6.9883   22.3042 C   0  0  0  0  0  0
    0.8840    7.1094   23.8539 C   0  0  0  0  0  0
    0.1007    8.1332   21.6760 C   0  0  0  0  0  0
   -0.1257    6.7465   24.4613 O   0  0  0  0  0  0
    1.9559    7.6325   24.4759 N   0  0  0  0  0  0
    0.9264    9.3760   21.3740 C   0  0  0  0  0  0
    2.1242    7.8085   25.9208 C   0  0  1  0  0  0
    1.5512    9.5358   20.1194 C   0  0  0  0  0  0
    1.0887   10.3742   22.3570 C   0  0  0  0  0  0
    3.4722    7.2469   26.3919 C   0  0  0  0  0  0
    1.7964    9.2425   26.3963 C   0  0  0  0  0  0
    2.3648   10.6567   19.8673 C   0  0  0  0  0  0
    1.8942   11.5002   22.1018 C   0  0  0  0  0  0
    3.4888    6.2142   27.0561 O   0  0  0  0  0  0
    4.6188    7.8580   26.0568 N   0  0  0  0  0  0
    2.6369   10.3760   25.7768 C   0  0  0  0  0  0
    2.5397   11.6366   20.8599 C   0  0  0  0  0  0
    4.8138    9.1019   25.3276 C   0  0  0  0  0  0
    4.1501   10.2988   26.0247 C   0  0  0  0  0  0

我有一个文件列表,其中包含我希望执行此搜索和提取的所有文件的前缀,因此我认为 for 语句将是要走的路。这是我最初的尝试:

for i in $(cat ligand-list.txt)
> do
> grep " [A-Z] " ../glide-dock_SP_8CHM/ligands/$i.sdf | grep " 'F\|S\|C\|O\|N " | awk '{print $1,$2,$3}' > $i_8CHM.xyz
> done
grep: ../glide-dock_SP_8CHM/ligands/ligand-list.txt.sdf: No such file or directory

我知道我需要在 grep 命令中的变量周围进行某种字符转义,所以我想使用双引号,但这产生了与上面相同的输出。添加带有双引号的转义字符会将$i视为文本字符串:

for i in $(cat ligand-list.txt)
> do
> grep " [A-Z] " ../glide-dock_SP_8CHM/ligands/"\$i".sdf | grep " 'F\|S\|C\|O\|N " | awk '{print $1,$2,$3}' > $i_8CHM.xyz
> done
.
.
.
grep: ../glide-dock_SP_8CHM/ligands/$i.sdf: No such file or directory

添加波浪形大括号是我能够得到的最接近的,因为我的 for 变量作为字符串传递给 grep,但 grep 现在将转义字符和波浪形大括号视为 bash 变量中的字符串:

for i in $(cat ligand-list.txt)
> do
> grep " [A-Z] " ../glide-dock_SP_8CHM/ligands/"\{$i}".sdf | grep " 'F\|S\|C\|O\|N " | awk '{print $1,$2,$3}' > $i_8CHM.xyz
> done
.
.
.
grep: ../glide-dock_SP_8CHM/ligands/\{3579831839}.sdf: No such file or directory

,“,{} 的任何其他组合或位置变化都会产生相同的结果。根据我对 SO 和 grep 文档的阅读,我认为我只需要双引号即可将我的 bash 变量作为文字字符串传递,但我认为这只是针对 grep 搜索的正则表达式,而不是 grep 正在搜索的文件。非常感谢对此的澄清,因为我一天中的大部分时间都在用这个敲击键盘。

bash awk grep

评论

1赞 Charles Duffy 11/2/2023
就够了。如果这不起作用,你就做错了其他事情(而且是错误的,所以这里实际上还有其他问题)。"$i"for i in $(cat anything)
0赞 Charles Duffy 11/2/2023
有关某些上下文,请参阅 DontReadLinesWithFor
0赞 Charles Duffy 11/2/2023
...无论如何,我建议你接下来使用xtrace日志记录来比较你使用时得到的命令和这些命令需要什么才能正常工作。,或者将脚本放入您希望它开始跟踪的位置以及您希望它禁用跟踪的位置。"$i"bash -x yourscriptset -xset +x
0赞 Charles Duffy 11/2/2023
另外,愚蠢的问题,但确实存在?由于是相对路径而不是绝对路径,因此它是否有效取决于您当前的工作目录,该目录不一定与您保存脚本的目录相同;考虑显式记录 的值,并确保查找不是从预期位置以外的其他位置开始的。../glide-dock_SP_8CHM/ligands/ligand-list.txt.sdf.."$PWD"/../glide-dock_SP_8CHM/ligands/"$i".sdf
2赞 Charles Duffy 11/2/2023
(最后一个资源指针:有关逐行读取文件的最佳实践方法,请参阅 BashFAQ #1)

答:

3赞 markp-fuso 11/2/2023 #1

设置:

$ mkdir -p ../glide-dock_SP_8CHM/ligands

$ cat ligand-list.txt
12345
78900
ABCDE

$ head ../glide-dock_SP_8CHM/ligands/*sdf
==> ../glide-dock_SP_8CHM/ligands/12345.sdf <==
   99.9999   99.9999   12345 A   0  0  0  0  0  0
    2.7118    4.0281   12345 F   0  0  0  0  0  0
   99.9999   99.9999   12345 G   0  0  0  0  0  0
    4.0921    3.8708   12345 S   0  0  0  0  0  0

==> ../glide-dock_SP_8CHM/ligands/78900.sdf <==
   99.9999   99.9999   78900 B   0  0  0  0  0  0
    2.7118    4.0281   78900 C   0  0  0  0  0  0
   99.9999   99.9999   78900 M   0  0  0  0  0  0
    4.0921    3.8708   78900 O   0  0  0  0  0  0

==> ../glide-dock_SP_8CHM/ligands/ABCDE.sdf <==
   99.9999   99.9999   ABCDE L   0  0  0  0  0  0
    2.7118    4.0281   ABCDE O   0  0  0  0  0  0
   99.9999   99.9999   ABCDE Z   0  0  0  0  0  0
    4.0921    3.8708   ABCDE F   0  0  0  0  0  0

笔记:

  • 对于我们不感兴趣的行,第 1/2 列始终为 99.9999
  • 对于感兴趣的行,第 3 列更改为文件名(更容易查看结果)

修改 OP 的代码:bash / while

while read -r i
do
    grep '[[:upper:]]' ../glide-dock_SP_8CHM/ligands/"$i".sdf | grep " [FSCON] " | awk '{print $1,$2,$3}' > "$i"_8CHM.xyz
done < ligand-list.txt

合并 2 倍呼叫:grep

while read -r i
do
    grep " [FSCON] " ../glide-dock_SP_8CHM/ligands/"$i".sdf | awk '{print $1,$2,$3}' > "$i"_8CHM.xyz
done < ligand-list.txt

删除通话:grep

while read -r i
do
    awk '$4 ~ /^[FSCON]$/ {print $1,$2,$3}' ../glide-dock_SP_8CHM/ligands/"$i".sdf > "$i"_8CHM.xyz
done < ligand-list.txt

使用循环构建文件列表,然后使用单个脚本来处理所述文件列表:whileawk

file_list=()

while read -r line
do
    file_list+=("../glide-dock_SP_8CHM/ligands/${line}.sdf")
done < ligand-list.txt

awk '
FNR==1          { close(outf)                       # close previous output file
                  n = split(FILENAME,a,/[/.]/)      # split current FILENAME on dual characters "/" and "."
                  outf = a[n-1] "_8CHM.xyz"         # create output filename from (n-1)st entry from array a[]
                 }
$4 ~ /^[FSCON]$/ { print $1, $2, $3 > outf }        # print desired columns to output file
' "${file_list[@]}"

笔记:

  • 所有这些解决方案都假定每个条目都存在一个文件,否则我们将生成错误ligand-list.txt'No such file or directory'
  • 如果文件可能不存在,则可以更新每个解决方案以包含一个测试,以在尝试处理所述文件之前验证文件是否存在

这些都会产生:

$ head *_8CHM.xyz
==> 12345_8CHM.xyz <==
2.7118 4.0281 12345
4.0921 3.8708 12345

==> 78900_8CHM.xyz <==
2.7118 4.0281 78900
4.0921 3.8708 78900

==> ABCDE_8CHM.xyz <==
2.7118 4.0281 ABCDE
4.0921 3.8708 ABCDE

评论

0赞 Charles Duffy 11/2/2023
作为避免未加引号的扩展的替代方案 - 想想file_list=( $(awk '{print "../glide-dock_SP_8CHM/ligands/"$0".sdf"}' ligand-list.txt) )readarray -t ligand_list <ligand-list.txt; file_list=( "${ligand_list[@]/#/'../glide-dock_SP_8CHM/ligands/'}" ); file_list=( "${file_list[@]/%/.sdf}" )
0赞 markp-fuso 11/2/2023
@CharlesDuffy,只要没有嵌入空格,文件列表构建的初始尝试就会奏效;想出了一个类似的方法,但有点笨拙,所以选择了更简单的方法readarray/mapfilewhile/read/array+=()
0赞 blakem 11/2/2023
@markp-fuso 感谢您的详细工作和逆向工程,我的最小工作示例。你是对的,这些方法中的每一种都有效,我很高兴能够使用这些方法来推进我的项目。我没有做的是在我的配体列表.txt中逐行查看,发现还有一行进入了我的文件:“配体列表.txt”。所以我从 grep 输出的错误正是它所说的我得到的。