Help us understand the problem. What is going on with this article?

言語処理100本ノック第2章で学ぶUNIXコマンド

はじめに

社内のメンバーを中心にした勉強会で言語処理100本ノックを解いているのですが、その解答コードや、解く過程で便利だなと思った小技のまとめです。自分で調べたり検証したりした内容が多いですが、他の勉強会メンバーが共有してくれた情報も入っています。

今回はUNIXコマンドの基礎についてまとめていきますが、先行する@moriwoさん@segavvyさんの記事でかなり詳しい解説がすでに書かれているので、この記事での説明はさらっと控えめにしたいと思います。以下を読んで分からない点があれば、上のリンクからお二方の記事なども見てみるのがおすすめです。

シリーズ

環境

  • macOS
  • Python 3.8.1
  • JupyterLab

コード

10. 行数のカウント

Python
def count_lines():
    with open('hightemp.txt') as file:
        return len(file.readlines())

count_lines()
結果(Python)
24
UNIX
!wc -l hightemp.txt
結果(UNIX)
      24 hightemp.txt

UNIXコマンドの方が圧倒的に簡潔に書けました。ちなみにwcの前についている!は、JupyterLabやNotebookでUNIXコマンドを実行するときに使うものです(場合によって!なしでも動作します)。

11. タブをスペースに置換

Python
def replace_tabs():
    with open('hightemp.txt') as file:
        return file.read().replace('\t', ' ')

print(replace_tabs())
UNIX
!cat hightemp.txt | sed $'s/\t/ /g'
結果(PythonとUNIX共通)
高知県 江川崎 41 2013-08-12
埼玉県 熊谷 40.9 2007-08-16
岐阜県 多治見 40.9 2007-08-16
山形県 山形 40.8 1933-07-25
山梨県 甲府 40.7 2013-08-10
...

UNIXのsedについて、$記号をつけないと\tがタブ記号として認識されないという点には注意が必要かなと思いました。

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

Python
import pandas as pd

def separate_columns():
    df = pd.read_csv('hightemp.txt', sep='\t', header=None)
    df.iloc[:,0].to_csv('col1.txt', header=False, index=False)
    df.iloc[:,1].to_csv('col2.txt', header=False, index=False)

separate_columns()
UNIX
!cut -f 1 hightemp.txt > col1_unix.txt
!cut -f 2 hightemp.txt > col2_unix.txt

結果を!head col1.txt col2.txtで確認すると、次のようになります。!head col1_unix.txt col2_unix.txtとした場合も同様です。

結果(Python)
==> col1.txt <==
高知県
埼玉県
...
==> col2.txt <==
江川崎
熊谷
...

13. col1.txtとcol2.txtをマージ

Python
def merge_columns():
    with open('col1.txt') as col1_file, open('col2.txt') as col2_file, \
         open('merge.txt', mode='w') as new_file:

        for col1_line, col2_line in zip(col1_file, col2_file):
            new_file.write(f'{col1_line.rstrip()}\t{col2_line.rstrip()}\n')

merge_columns()
UNIX
!paste col[1-2].txt > merge_unix.txt
結果(PythonとUNIX共通)
高知県   江川崎
埼玉県   熊谷
岐阜県   多治見
山形県   山形
...

結果の確認については!head merge.txtまたは!head merge_unix.txtとすると、上のような出力が得られるはずです。

14. 先頭からN行を出力

Python
def show_head():
    n = int(input())

    with open('hightemp.txt') as file:
        for line in file.readlines()[:n]:
            print(line.rstrip())

show_head()
UNIX
!head -3 hightemp.txt
結果(PythonとUNIX共通)
高知県   江川崎   41  2013-08-12
埼玉県   熊谷  40.9    2007-08-16
岐阜県   多治見   40.9    2007-08-16

Pythonについては、関数からリストを返すことにしたい場合、以下のように書くのもありかと思います。

Python
def show_head():
    n = int(input())

    with open('hightemp.txt') as file:
        return [line for line in file.readlines()[:n]]

print(*show_head())

一方UNIXではPythonのようにコマンドラインで整数を受け取ることが難しいのですが、-の後ろに整数を書くと何行分表示するか指定できます。応用的な使い方としては、たとえば12の解答の最後で

UNIX
!cat hightemp.txt | sed $'s/\t/ /g' | head -5

と書いて、最初の5行だけ表示することもできたりします。

15. 末尾のN行を出力

Python
def show_tail():
    n = int(input())

    with open('hightemp.txt') as file:
        return [line for line in file.readlines()[-n:]]

print(*show_tail())
UNIX
!tail -3 hightemp.txt
結果(PythonとUNIX共通)
山梨県   大月  39.9    1990-07-19
山形県   鶴岡  39.9    1978-08-03
愛知県   名古屋   39.9    1942-08-02

14とほぼ同じ。

16. ファイルをN分割する

Python
import math

def split_file():
    n = int(input())

    with open('hightemp.txt') as file:
        lines = file.readlines()
        num = math.ceil(len(lines) / n)
        for i in range(n):
            with open('split{}.txt'.format(i + 1), mode='w') as new_file:
                text = ''.join(lines[i * num:(i + 1) * num])
                new_file.write(text)

split_file()
UNIX
!split -n 5 -d hightemp.txt split_unix
結果(Python)
==> split1.txt <==
高知県   江川崎   41  2013-08-12
埼玉県   熊谷  40.9    2007-08-16
岐阜県   多治見   40.9    2007-08-16
山形県   山形  40.8    1933-07-25
山梨県   甲府  40.7    2013-08-10

==> split5.txt <==
大阪府   豊中  39.9    1994-08-08
山梨県   大月  39.9    1990-07-19
山形県   鶴岡  39.9    1978-08-03
愛知県   名古屋   39.9    1942-08-02

上記は、Pythonの出力を!head split1.txt split5.txtで確認したものです。

一方、上のUNIXコマンドは自分の環境だと動作せず、これだけはColab(Google Colaboratory)で試しました。Linuxでは一般に-nコマンドが提供されているようなのですが(@IT)、デフォルトのmacOSでは使えないようです。

Colabで上記を実行するとsplit_unix00からsplit_unix04まで5つのファイルができたのですが、加えてそこに拡張子のtxtをつけようとしたりすると、ちょっと面倒になりそうだと感じました。@moriwoさんの記事ではawkなども使った実装例が紹介されていますが、自分はPythonを使ったほうが読みやすいコードになるのかな、と思いました。

17. 1列目の文字列の異なり

Python
import pandas as pd

def get_chars_set():
    df = pd.read_csv('hightemp.txt', sep='\t', header=None)
    return set(df.iloc[:, 0])

print(get_chars_set())
結果(Python)
{'千葉県', '埼玉県', '山形県', '和歌山県', '静岡県', '高知県', '大阪府', '岐阜県', '群馬県', '愛媛県', '山梨県', '愛知県'}
UNIX
!sort -u col1_unix.txt
結果(UNIX)
千葉県
埼玉県
大阪府
山形県
...

UNIXコマンドは並べ替えと重複除去の処理をパイプで繋いで、!sort col1_unix.txt | uniqと書くこともできます。

18. 各行を3コラム目の数値の降順にソート

Python
def sort_rows():
    df = pd.read_csv('hightemp.txt', sep='\t', header=None)
    df.rename(columns={0: 'Prefect', 1: 'City', 2: 'Temp', 3: 'Date'}, inplace=True)
    df.sort_values(by='Temp', inplace=True)
    return df

sort_rows()
結果(Python)
   Prefect  City  Temp        Date
23     愛知県   名古屋  39.9  1942-08-02
21     山梨県    大月  39.9  1990-07-19
20     大阪府    豊中  39.9  1994-08-08
...
UNIX
!sort hightemp.txt -k 3
結果(UNIX)
愛知県   名古屋   39.9    1942-08-02
山形県   鶴岡  39.9    1978-08-03
山梨県   大月  39.9    1990-07-19
大阪府   豊中  39.9    1994-08-08
...

「逆順」という言葉の意味が、元の逆ということなのか降順ということなのか判然としませんが、後者の解釈で解いてみました。この問題については、UNIXコマンドだと短く書けるということを特に強く感じられますね。

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

Python
def count_freq():
    df = pd.read_csv('hightemp.txt', sep='\t', header=None)
    return df[0].value_counts()

count_freq()
結果(Python)
群馬県     3
山梨県     3
山形県     3
埼玉県     3
UNIX
!cut -f 1 hightemp.txt | sort | uniq -c | sort -r
結果(UNIX)
   3 群馬県
   3 山梨県
   3 山形県
   3 埼玉県

UNIXコマンドは少し長くなりましたが、まず1列目を切り出して(cut -f 1)、次にその出現頻度を求め(sort | uniq -c)、最後に出現頻度の逆順で並べる(sort -r)、という流れが分かりやすくなるように書いてみました。

ただ、pandasのvalue_counts()の優秀さも考えると、ここではPythonを使ったほうが分かりやすいかなという気がします。

まとめ

この章については以上ですが、間違いなどあったらコメントいただけると助かります。

Yusuke196
器用貧乏まっしぐら
https://twitter.com/yusuke196
and-d
新しい技術を活用した調査や分析によってクライアントのマーケティングを支援するリサーチ会社です。自然言語処理からデータ分析の自動化アルゴリズム基盤構成まで、幅広いアプローチによる開発を手掛けています。
https://www.and-d.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした