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
最初のフィールドは行番号です。
いくつかの行番号引数を使用してスクリプトを呼び出すことができ、.txtがその行番号の最初と2番目のフィールドを印刷できるようにシェルスクリプトを作成しようとしていますdata.txt
。たとえば、次のようget.sh 1 3 5
に印刷する必要があります。
1 aFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
3 cFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
5 eFDLKSFD_FDSJFskadfsff_fsadklfj_fdsaf
awkは最初と2番目のフィールドを印刷するためにのみ使用できると思いますが、シェルスクリプトに渡されたパラメータに基づいて特定の行のみをフィルタリングすることに閉じ込められています。よろしくお願いします。
答え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
スペースとコンマに沿って変数を配列に分割し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;
}' "$@"
これは、ファイル名ではなくパラメータに基づいて正規表現を作成し、後で各ファイルの行番号を一致させるために使用されます。一致する場合は、入力行をスペースに分割し、最初の2つのフィールドをタブで区切って印刷します。
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の「1行」を囲む無意味なbashラッパーではなく、Perlスクリプトでなければなりません。しかし、人々は1行のコードが「正しい」と思うようですが、#!/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
これは実際に正確にPerlインタプリタだけをフォークするシェルインタプリタをフォークするのに最小限の時間とメモリを無駄にしないことも同様です。
さらに重要なことは、次の問題を回避することです。シェルリファレンスシェルが含まれていないからです。返品、構文強調スクリプトはシェルスクリプトで一重引用符で囲まれた文字列ではないため、エディタではうまく機能します。そして行番号警告/エラーメッセージは、単一行内の相対行番号ではなくスクリプトファイルの絶対行番号を参照するため、スクリプトをデバッグするときは正確です。
答え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