特定の列を名前で印刷する方法は?


32

次のファイルがあります。

id  name  age
1   ed    50
2   joe   70   

idage列だけを印刷したい。今私はちょうど使用しますawk

cat file.tsv | awk '{ print $1, $3 }'

ただし、これには列番号を知る必要があります。列番号の代わりに列の名前(最初の行で指定された)を使用できる場所でそれを行う方法はありますか?


7
cat必要ありません、ところで。使用できますawk '{ print $1, $3 }' file.tsv
エリックウィルソン

列番号でない場合、に依存しますか?
rozcietrzewiacz

2
@rozcietrzewiacz名前。彼はid代わりに$1age代わりに言いたい$3
マイケルMrozek

回答:


37

たぶんこのようなもの:

$ cat t.awk
NR==1 {
    for (i=1; i<=NF; i++) {
        ix[$i] = i
    }
}
NR>1 {
    print $ix[c1], $ix[c2]
}
$ awk -f t.awk c1=id c2=name input 
1 ed
2 joe
$ awk -f t.awk c1=age c2=name input 
50 ed
70 joe

コマンドラインで印刷する列を指定する場合は、次のようにします。

$ cat t.awk 
BEGIN {
    split(cols,out,",")
}
NR==1 {
    for (i=1; i<=NF; i++)
        ix[$i] = i
}
NR>1 {
    for (i in out)
        printf "%s%s", $ix[out[i]], OFS
    print ""
}
$ awk -f t.awk -v cols=name,age,id,name,id input 
ed 1 ed 50 1 
joe 2 joe 70 2 

(ブロックで-v定義された変数を取得するスイッチに注意してくださいBEGIN。)


私はawkの学習を延期しています...可変列数をサポートする最良の方法は何ですか?awk -f t.awk col1 col2 ... coln input理想的です。awk -f t.awk cols=col1,col2,...,coln inputうまくいくだろう
ブレットトーマス

1
私の答えを更新しました。あなたがそれで何かをしたいなら、それを学ぶの
マット

3
2番目の例では、列が期待どおりの順序で出力されfor (i in out)ず、固有の順序はありません。ソリューションとしてgawk提供PROCINFO["sorted_in"]する場合は、for( ; ; )おそらくaを使用してインデックスを反復処理することをお勧めします。
mr.spuratic

@BrettThomas、このチュートリアルを強くお勧めます。(lynda.comにアクセスできる場合は、「Awkエッセンシャルトレーニング」をお勧めします。これは、すべて同じ素材をより簡潔に練習演習でカバーしています。)
ワイルドカード

氏スプラーティック、あなたはダマン。私はfor(i in out)問題に出くわし、3つのフィールドでうまく働きました。 。あなたがしなければならないためにそれらを取得するには(私は1 =; I <=長さ(アウト); I ++)
Severun

5

Perlソリューションをくじ引きするだけです:

#!/usr/bin/perl -wnla

BEGIN {
    @f = ('id', 'age');   # field names to print
    print "@f";           # print field names
}

if ($. == 1) {            # if line number 1
    @n = @F;              #   get all field names
} else {                  # or else
    @v{@n} = @F;          #   map field names to values
    print "@v{@f}";       #   print values based on names
}

5

csvkit

入力データは、CSV形式に変換し、そのようなものとしてCSVツールを使用してcsvcutからcsvkit

$ cat test-cols.dat 
id  name  age
1   ed    50
2   joe   70 

csvkitをインストールします。

$ pip install csvkit

使用しtrてスクイズオプションで-s有効なCSVファイルに変換し、適用しますcsvcut

$ cat test-cols.dat | tr -s ' ' ',' | csvcut -c id,age
id,age
1,50
2,70

古いデータ形式に戻したい場合は、次を使用できます。 tr ',' ' ' | column -t

$ cat test-cols.dat | tr -s ' ' ',' | csvcut -c id,age | tr ',' ' ' | column -t
id  age
1   50
2   70

ノート

  • csvkitは異なる区切り文字(共有オプション -dまたは--delimiter)もサポートしていますが、csvファイルを返します。

    • ファイルで列を区切るのにスペースのみを使用する場合(タブはまったく使用しない)、次のように動作します

      $ csvcut -d ' ' -S -c 'id,age' test-cols.dat
      id,age
      1,50
      2,70
    • ファイルでタブを使用して列を区切る場合、次のように動作し、csvformattsvファイルを取得するために使用できます。

      $ csvcut -t -c 'id,age' test-cols.dat | csvformat -T
      id  age
      1   50
      2   70

      確認した限りでは、1つのタブのみが許可されています。

  • csvlook テーブルをマークダウンテーブル形式でフォーマットできます。

    $ csvcut -t -c "id,age" test-cols.dat | csvlook
    | id | age |
    | -- | --- |
    |  1 |  50 |
    |  2 |  70 |
  • UUOC(猫の無駄な使用):コマンドを作成するのにこの方法が好きです。


+1。しかし、の不必要な使用trも。TSVファイルは、CSVに変換する必要なく、直接サポートされます。-t(別名--tabs)オプションが伝えcvscutフィールド区切り文字としてタブを使用します。そして、-dまたは--delimiter区切り文字として任意の文字を使用します。
cas

いくつかのテストでは-d-tオプションとオプションは半破損しているようです。入力区切り文字を指定するように機能しますが、出力区切り文字は常にコンマになるようにハードコーディングされています。壊れたIMO-入力区切り文字と同じか、ユーザーがawkFSやOFS変数などの出力区切り文字を設定できるようにする別のオプションが必要です。
cas

4

数字の代わりに名前で それらのフィールドを参照したいだけなら、使用できますread

while read id name age
do
  echo "$id $age"
done < file.tsv 

編集

私はついにあなたの意味を見ました!コマンドラインで指定した列のみを出力するbash関数は次のとおりです(by名前で)。

printColumns () 
{ 
read names
while read $names; do
    for col in $*
    do
        eval "printf '%s ' \$$col"
    done
    echo
done
}

提示したファイルでそれを使用する方法は次のとおりです。

$ < file.tsv printColumns id name
1 ed 
2 joe 

(関数 はstdin。を読み取ります。< file.tsv printColumns ...printColumns ... < file.tsvcat file.tsv | printColumns ...

$ < file.tsv printColumns name age
ed 50 
joe 70 

$ < file.tsv printColumns name age id name name name
ed 50 1 ed ed ed 
joe 70 2 joe joe joe

注:要求する列の名前に注意してください!このバージョンには健全性チェックがないため、引数の1つが次のような場合に厄介なことが発生する可能性があります"anything; rm /my/precious/file"


1
これには、列番号を知る必要もあります。idnameおよびageに名前を付けたからといって、注文がreadラインにハードコードされているという事実は変わりません。
ジャンモーセン

1
@janmoesenはい、私はついにポイントを得ました:)
rozcietrzewiacz

いいですね、ありがとう。私は大きなファイル(1000列、数百万行)で作業しているので、速度のためにawkを使用しています。
ブレットトーマス

@BrettThomasああ、なるほど。私はとても興味があります:時間比較を提供するベンチマークを投稿できますか?(を使用time { command(s); })。
rozcietrzewiacz

@rozceitrewaicz:time cat temp.txt | ./col1 CHR POS > /dev/null 99.144u 38.966s 2:19.27 99.1% 0+0k 0+0io 0pf+0w time awk -f col2 c1=CHR c2=POS temp.txt > /dev/null 0.294u 0.127s 0:00.50 82.0% 0+0k 0+0io 0pf+0w
ブレットトーマス

3

それが価値があるもののために。これにより、ソース内の任意の数の列、および選択した出力シーケンスで印刷する任意の数の列を処理できます。引数を再配置するだけです...

例えば。コール:script-name id age

outseq=($@)
colnum=($( 
  for ((i; i<${#outseq[@]}; i++)) ;do 
    head -n 1 file |
     sed -r 's/ +/\n/g' |
      sed -nr "/^${outseq[$i]}$/="
  done ))
tr ' ' '\t' <<<"${outseq[@]}"
sed -nr '1!{s/ +/\t/gp}' file |
  cut -f $(tr ' ' ','<<<"${colnum[@]}") 

出力

id      age
1       50
2       70

2

読んでいるファイル がユーザー生成される可能性がない場合、readビルトインを悪用する可能性があります。

f=file.tsv
read $(head -n1 "$f") extra <<<`seq 100`
awk "{print \$$id, \$$age}" "$f"

入力ファイルの最初の行全体が引数リストに代入されるためread、ヘッダー行のすべてのフィールド名が変数名として渡されます。これらの最初の1が割り当てられますseq 100生成 2番目には2、3番目には3ます。過剰なseq出力は、ダミー変数によって吸収されますextra。事前に入力列の数がわかっている場合は、100を変更してに一致させて削除することができますextra

awkこのスクリプトは、によって定義されたシェル変数できるように、二重引用符で囲まれた文字列であるreadとしてスクリプトに代入するawkフィールド番号を。


1

通常、ファイルヘッダーを見て、必要な列の数を数えて(c)Unixを使用する方が簡単ですcut

cut -f c -d, file.csv

しかし、多くの列または多くのファイルがある場合、次のfollowingいトリックを使用します。

cut \
  -f $(head -1 file.csv | sed 's/,/\'$'\n/g' | grep -n 'column name' | cut -f1 -d,) \
  -d, \ 
  file.csv

OSXでテストされ、file.csvカンマ区切りです。


1

単一の列を選択する簡単な方法を次に示します。

「foo」という名前の列が必要だとします。

f=file.csv; colnum=`head -1 ${f} | sed 's/,/\n/g' | nl | grep 'foo$' | cut -f 1 `; cut -d, -f ${colnum} ${f}

基本的に、ヘッダー行を取得し、行ごとに1つの列名を持つ複数の行に分割し、行に番号を付け、目的の名前の行を選択し、関連する行番号を取得します。次に、その行番号をcutコマンドの列番号として使用します。


0

同様の解決策を探しています(idという名前の列が必要で、列番号が異なる場合があります)。

head -n 1 file.csv | awk -F',' ' {
      for(i=1;i < NF;i++) {
         if($i ~ /id/) { print i }
      }
} '

0

この目的のために、基本的に次のように機能するPythonスクリプトを作成しました。

with fileinput.input(args.file) as data:
    headers = data.readline().split()
    selectors = [any(string in header for string in args.fixed_strings) or
                 any(re.search(pat, header) for pat in args.python_regexp)
                 for header in headers]

    print(*itertools.compress(headers, selectors))
    for line in data:
        print(*itertools.compress(line.split(), selectors))

私はこれhgrepヘッダーgrepのために呼び出しました。次のように使用できます。

$ hgrep data.txt -F foo bar -P ^baz$
$ hgrep -F foo bar -P ^baz$ -- data.txt
$ grep -v spam data.txt | hgrep -F foo bar -P ^baz$

argparseコマンドライン引数の解析に使用するため、スクリプト全体が少し長くなります。コードは次のとおりです。

#!/usr/bin/python3

import argparse
import fileinput
import itertools
import re
import sys
import textwrap


def underline(s):
    return '\033[4m{}\033[0m'.format(s)


parser = argparse.ArgumentParser(
    usage='%(prog)s [OPTIONS] {} [FILE]'.format(
        underline('column-specification')),
    description=
        'Print selected columns by specifying patterns to match the headers.',
    epilog=textwrap.dedent('''\
    examples:
      $ %(prog)s data.txt -F foo bar -P ^baz$
      $ %(prog)s -F foo bar -P ^baz$ -- data.txt
      $ grep -v spam data.txt | %(prog)s -F foo bar -P ^baz$
    '''),
    formatter_class=argparse.RawTextHelpFormatter,
)

parser.add_argument(
    '-d', '--debug', action='store_true', help='include debugging information')
parser.add_argument(
    'file', metavar='FILE', nargs='?', default='-',
    help="use %(metavar)s as input, default is '-' for standard input")
spec = parser.add_argument_group(
    'column specification', 'one of these or both must be provided:')
spec.add_argument(
    '-F', '--fixed-strings', metavar='STRING', nargs='*', default=[],
    help='show columns containing %(metavar)s in header\n\n')
spec.add_argument(
    '-P', '--python-regexp', metavar='PATTERN', nargs='*', default=[],
    help='show a column if its header matches any %(metavar)s')

args = parser.parse_args()

if args.debug:
    for k, v in sorted(vars(args).items()):
        print('{}: debug: {:>15}: {}'.format(parser.prog, k, v),
              file=sys.stderr)

if not args.fixed_strings and not args.python_regexp:
    parser.error('no column specifications given')


try:
    with fileinput.input(args.file) as data:
        headers = data.readline().split()
        selectors = [any(string in header for string in args.fixed_strings) or
                     any(re.search(pat, header) for pat in args.python_regexp)
                     for header in headers]

        print(*itertools.compress(headers, selectors))
        for line in data:
            print(*itertools.compress(line.split(), selectors))

except BrokenPipeError:
    sys.exit(1)
except KeyboardInterrupt:
    print()
    sys.exit(1)

0

awk、そのすべてのヴィンテージについて、本質的に整数インデックスが付けられていcutます。

名前がインデックス化されたデータを処理するように設計されたいくつかのツールを次に示します(そのほとんどは、非常に一般的なファイル形式であるCSVとTSVのみを処理します)。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.