如何提取作为参数传递给 bash 脚本的特定行号的字段?

如何提取作为参数传递给 bash 脚本的特定行号的字段?

我有一个名为data.txt以下内​​容的文件:

 1           aFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 2           bFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 3           cFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 4           dFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 5           eFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 6           fFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 7           gFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 8           hFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
 9           iFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
10           jFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234
11           kFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf        IT524234

请注意,第一个字段是行号。

现在我想构建一个 shell 脚本,以便我可以使用一些行号参数调用该脚本,并且它应该打印出 .txt 中相应行号的第一个和第二个字段data.txt。例如: get.sh 1 3 5应该打印:

1           aFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
3           cFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
5           eFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf

我认为 awk 只能用于打印第一个和第二个字段,但我坚持根据传递给 shell 脚本的参数仅过滤特定行。提前致谢。

答案1

在 awk 中,您可以将行号收集到数组中并读取一次文件,打印数组中提到的行:

#!/bin/sh
awk -v lines="$*" 'BEGIN { split(lines, a, "[, ]"); 
                           for (i in a) b[a[i]] = 1;}
                   NR in b {print $1, $2}' < data.txt

沿空格和逗号将变量拆分为 array ,split()并且循环构建数组,使得linesaforb该数组的包含我们感兴趣的行。然后NR in b只需检查与当前行号匹配的键是否存在。

请注意,每行只会打印一次,无论它在输入中存在多少次,并且这些行将按输入数字顺序打印,而不是参数给出的顺序:

$ bash get.sh 7 3 3
3 cFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
7 gFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf

get.sh 7,3,3也有效)

答案2

将以下行放入文本文件中并将其命名为 get.sh。然后使其可执行。

#!/bin/sh
## this is GNU sed
sed -En "
  $(printf '%sbp\n' "$@" "d;")
  :p;s/\S+/&\n/2;P
" data.txt

现在调用脚本,如下所示:

chmod +x ./get.sh
./get.sh 1 3 5

答案3

#!/bin/bash

perl -le '
for (@ARGV) {
  # separate command line args into filename(s) and line-number(s)
  # line-numbers can be space and/or comma separated.
  if (-e $_) { push @files, $_ } else { push @lines, split /,/};
};

@ARGV = @files;
$re = join("|",@lines);

while(<>) {
  print join("\t",(split)[0..1]) if ($. =~ m/^($re)$/);
  close(ARGV) if eof;
}' "$@"

这会根据非文件名参数构建一个正则表达式,稍后使用该正则表达式来匹配每个文件的行号。在匹配时,它用空格分割输入行并打印由制表符分隔的前两个字段。

之所以需要它,close(ARGV)只是因为我们关心当前文件的行号,而不是到目前为止看到的所有输入的行号。 perl 仅在文件句柄关闭时重置$.(又名$NR$INPUT_LINE_NUMBER)变量,但文件句柄通常不会在while(<>)循环中关闭。这只是显式关闭文件句柄以便$.重置。看perldoc -f eof

$ ./get.sh 1 3,5 data.txt 
1       aFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
3       cFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
5       eFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf

当然,这个脚本应该是一个 Perl 脚本,而不是围绕 Perl“一行”的毫无意义的 bash 包装器。但人们似乎认为单行代码是“正确的”,而使用除 #!/bin/bash 或 #!/bin/sh 之外的任何内容作为解释器的脚本在某种程度上是错误的。

#!/usr/bin/perl -l

for (@ARGV) {
  # separate command line args into filename(s) and line-number(s)
  # line-numbers can be space and/or comma separated.
  if (-e $_) { push @files, $_ } else { push @lines, split /,/ };
};

@ARGV = @files;
$re = join('|',@lines);

while(<>) {
  print join("\t",(split)[0..1]) if ($. =~ m/^($re)$/);
  close(ARGV) if eof;
};
$ ./get.pl 1 3,5 data.txt  
1       aFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
3       cFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
5       eFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf

这确实确切地同样的事情,不会浪费极少的时间和内存来分叉一个 shell 解释器,它除了分叉 Perl 解释器之外什么也不做。

更重要的是,它避免了以下问题外壳引用因为不涉及shell。还,语法高亮可以在编辑器中正常运行,因为脚本不仅仅是 shell 脚本中的单引号字符串。并且行号调试脚本时警告/错误消息是正确的,因为它们引用脚本文件的绝对行号,而不是单行内的相对行号。

答案4

#! /bin/bash
# get.sh
IFS=$'\n'
args=(`sort -nu <<<$*`)
unset IFS

awk -v lines="${args[*]}" 'BEGIN{split(lines, ar, " ");}{ for (i in ar) { if (NR == ar[i]) print $1,$2} }' data.txt

首先args创建一个包含排序且唯一值的数组。为此,我们使用了选项-n和。-u参见更多详情

然后从变量split创建一个数组。现在,如果元素等于记录号(NR),则循环打印所需的输出。arlinesar

相关内容