我有一个名为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()
并且循环构建数组,使得lines
a
for
b
键该数组的包含我们感兴趣的行。然后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),则循环打印所需的输出。ar
lines
ar