バイオインフォをやっている大学院生です。プログラミングのことなどを書きます。

Mac で接続した HDD を自動でマウントしない方法

TimeMachine を使って、HDD にバックアップをしているのですが、複数の Mac を使っているため、Mac によってマウントしてほしい HDD が違います。

しかし、ディスプレイをハブとして使用していて、いちいち HDD とディスプレイの接続を切り替えるのは面倒です。

ということで、指定した HDD が自動でマウントがされないようにする設定をしました。

ちなみにこの方法はパーティティションで指定ができるので、一つの HDD に複数のパーティティションが存在している場合でも使用できます。

全体の流れはこのようになります。

  1. UUID を確認する
  2. vifs で自動でマウントしないように設定する

1. UUID を確認する

ディスクユーティリティから確認する方法

まず、UUID を調べたいディスクを選択します。

情報をクリックすると、そのディスクについての情報の一覧が開きます。

その中のファイルシステム UUID という項目を控えておいてください。

ターミナルから確認する方法

diskutil info [ボリューム名]|grep UUID

複数の UUID が表示される場合があると思いますが、そのうちの Volume UUID という項目を控えておいてください。

2. vifs で自動でマウントしないように設定する

UUID が分かったら、次は vifs コマンドを使用して設定を行います。

ターミナルから次のコマンドを実行します。

sudo vifs

パスワードを要求された場合は入力してください。

エディタが開くので、次のように入力し保存します。

UUID=[取得した UUID] none auto noauto

それぞれの項目がどんな意味かはここを参照してください。

(Linux) fstabの書式 主要オプションなど | hara-chan.com

以上の手順により、自動でマウントがされなくなっているはずです。

手動でマウントを行う方法

ディスクユーティリティからマウントのボタンを押せば、手動でマウントをすることが可能です。

ターミナルからマウントをするには

diskutil mount [ボリューム名]

ただし、ロックされた APFS ボリュームをマウントする場合は次のコマンドを使用する必要がありました。

diskutil apfs unlockVolume [ボリューム名] -passphrase [パスフレーズ]

cronでプログラムを定期的に実行する

cronとは

Macでプログラムを定期的に実行する手法を紹介します。cronとは、UNIX系のOSに入っているプログラムで、時間を指定してプログラムを実行することができます。また、「毎週日曜日に」や「毎月1日に」などというように、繰り返し実行させることが可能です。

cronを動かしてみる

Macで試しました。実行した環境のOSはmacOS Big Sur、シェルはbashを使っています。 まず、実行したいスクリプトを用意します。今回は、わかりやすいようにターミナルに実行された時間を出力するプログラムにします。

シェルスクリプトの準備

print_date.sh というファイルを作成し、次のように変更します。

#!/usr/bin/env bash
term="/dev/ttys000" # ttyコマンドの結果を入力
date > $term

ttyコマンドは、そのターミナルの名前を得るコマンドです。ttyコマンドで得られた結果にリダイレクトすることで、ターミナルに実行された時間を出力させます。 次に、chmod 700 print_date.shとして、このシェルスクリプトに実行権限を与えます。 この作業を忘れると、この記事の通り作業しても、実行できなくなってしまうので、注意してください。

次に、このシェルスクリプトが定期的に実行されるように、設定をしていきます。

cronの設定

cronの設定にはcrontabというコマンドを使用します。 crontab -eと入力すると、cronの設定を編集することができます。 次の行を書き加えてください。編集ができたら、エディタを保存して閉じてください。cronのインストールの許可が求められた場合、許可をしてください。

* * * * * [print_date.shがあるフォルダのパス]/print_date.sh

ここでは最初に*を5つ並べて書いています。この部分が、実行する時間を示している部分です。

それぞれの*は、左から順に次の要素を表しています。

何番目 表しているもの 範囲
1 0-59
2 0-23
3 1-31
4 1-12
5 曜日 0-7(0, 7が日曜日)

先ほどのように* * * * *とすると、毎分実行されるように設定されます。 他にも次のような使用例があります。

* * * * 0   #毎週日曜日
* */1 * * *   # 1時間ごと
* 8-12 * * * #毎日8, 9, 10, 11, 12時に実行
* 0,6,12,18 * * *    #毎日0, 6,12,18時に実行
* * 13 * * 5 # 毎月13日と、毎週の金曜日

ちなみに、この5つのフィールドで指定する他にも、文字列で指定する方法もあるようです。

@reboot         Run once, at startup.
@yearly         Run once a year, "0 0 1 1 *".
@annually       (same as @yearly)
@monthly        Run once a month, "0 0 1 * *".
@weekly         Run once a week, "0 0 * * 0".
@daily          Run once a day, "0 0 * * *".
@midnight       (same as @daily)
@hourly  

実装によって指定方法や挙動の違いがある可能性がありますので、細かい使い方などはcrontabのマニュアルを参照してください。自分の環境ではman 5 crontabで書き方を参照することができました。

実行結果

cronを設定してしばらく経つと、次のように指定したコンソールに表示がされていきました。

Mon Jan 18 19:32:00 JST 2021
Mon Jan 18 19:33:00 JST 2021
Mon Jan 18 19:34:00 JST 2021
Mon Jan 18 19:35:00 JST 2021
Mon Jan 18 19:36:00 JST 2021
Mon Jan 18 19:37:00 JST 2021
Mon Jan 18 19:38:00 JST 2021
Mon Jan 18 19:39:00 JST 2021

一分ごとにきちんとコマンドが実行されていることがわかります。

設定したコマンドの消去

今設定したコマンドを消したい場合、設定した時と同様にcrontab -eとしてエディタを開きます。エディタで、先ほど追加した行を削除すれば大丈夫です。 また、削除方法として、crontab -rが紹介されていることもあります。このコマンドは、確かに設定したcrontabを消去します。しかし、crontabに複数のコマンドを指定した場合、全てのコマンドを消去してしまうので、注意が必要です。

cronが動かない

cronはコマンド実行の際に何かあるとメールを送信するようになっているようです。mailコマンドで確認すると、cronからエラーメッセージが届いている可能性があります。print_date.shが実行できるようにパーミッションを与えるのを忘れた場合、それを知らせるメッセージが届いていました。 また、/usr/lib/cron/cron.allow/usr/lib/cron/cron.denyというファイルによってcronを使用できるユーザーが制御されているため、これらのファイルを確認することが有用であるかもしれません。

Macでファイルの作成日時・更新日時を取得するコマンド

Macでファイルの作成日時などをまとめて表示させたい場合の方法。

stat -f "access:%Sa%nmodified:%Sm%ninode changed:%Sc%nbirthtime:%SB" test.txt

でできる。出力は

access:Nov 17 19:28:34 2020
modified:Nov 17 19:28:33 2020
inode changed:Nov 17 19:28:33 2020
birthtime:Nov 17 19:28:30 2020

のようになる。

コマンドの説明

stat がファイルの属性や作成した日付などを表示させるコマンド。
-fは出力のフォーマットの指定を表す。フォーマットを表すのが次の文字列。

フォーマットの説明

%a - アクセス日時
%m - 更新日時
%c - inodeが変更された日時
%B - ファイルを作成した日時
これらに対して、%SaのようにSをつけることで、、unix時間ではなく、読みやすい表記で表示されるようにしている。
フォーマットだと、\nではなく%nが改行になっているので注意。

Excelでセルをボタンにしてマクロを実行させるVBA

やりたいこと

f:id:feb_march:20200606160354p:plain
イメージ
このセルをダブルクリックすると、この上に新しい行を追加するマクロが実行されるようにしたい。(今回は行を追加することを目的とするが、マクロの中身は行を新しく追加するものである必要はなく、任意のマクロを行わせることができる。)

基本的な流れはここの内容を参考にした。 しかし、この記事で紹介されているボタンでは、セルの値が固定されている必要がある。行を追加する動作をさせたいので、ボタンの位置が移動してしまうのでこのままでは使えない。また、このボタンを複数個作成するので、関数を複数定義するのもいまいち。

そこで、ボタンの場所ではなく、値が"add row"であるセルをダブルクリックときに行を追加するマクロが実行できるようにする。

方法

まず、作業するブックで、マクロを有効であるとする。Excel2007以降は、拡張子がxlsxだとマクロなしになっている。もしxlsxだったら、拡張子をxlsmにして保存し直す。 なお、MacExcelで作業をした。Windowsの場合とはマクロの設定方法など細かい違いがあるかもしれないが、基本的には同じやりかたで可能だと思う。

1. ボタンを押したときに実行するマクロの定義

まず、ボタンを押したとき呼び出したいマクロを定義する。

f:id:feb_march:20200606161413p:plain
Mac版のばあいでのマクロの登録
新しいマクロの登録は、ツール→マクロ→新しいマクロの記録というところからできる。今回は選択したセル(ボタンにしたセル)をクリックしたときに、その上に新しい行を追加したい。だから、適当なセルを選んでおいて、マクロの記録を開始したら、右クリック→挿入→行全体を選択して、セルの上に新しい行を追加する。これでマクロの記録は終わりで、マクロの名前はadd_rowとしておく。

2. ボタンの作成

セルにしたいボタンに対して、"add row"と入力する。なお、これから"add row"と書かれたセルをボタンと判別するVBAをかくことになるので、ボタンの値は他のボタンでないセルとかぶらないように注意する。たとえば、"____add row____"などにすることで、偶然他のセルと値が被ってしまう可能性を下げられるだろう。 なお、見た目をボタンっぽくする方法はここで紹介されている。やってみたい方はここを参照してやってみてほしい。

kokodane.com

3. VBAの作成

"add row"と書いてあるセルがダブルクリックされたときに、1.で定義したマクロ"add_row"が呼ばれるVBAを書く。 まず、ツール→マクロ→Visual Basic Editorを開く。 機能を追加したいシートを左側のファイルナビゲータから開くと、エディタが表示される。そこに次のコードをかく

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not (Target.Value = "add row") Then Exit Sub
add_row
End Sub

2行目の"add row"は2.でボタンにしたセルの値を入力する。3行目は行いたいマクロの名称を指定している。マクロを他の名前で登録していたら、それに応じてなおしてほしい。

VBA script
VBAスクリプト

上の選択項目も画像のように直す。

テスト

"add row"としたセルをダブルクリックして、上に行が追加されることを確認した。また、このセルをコピペして他の場所にうつしても、同様に動作することを確認した。

GASで無料で定期的に実行するSlackボットを作る

Slackに定期的に投稿をするボットを作ります。

今回はSlackのWebAPIを設定して、Google Apps Script(GAS)からslackへ投稿する方法を紹介します。

GASはGoogleのサーバー上で動くサービスで、無料で使うことができます。 (制限があるので注意。すごく頻繁にやったりしなければslackのbotくらいなら引っかからないと思います。

【GAS】Google Apps Scriptで注意すべき制限まとめ【実行回数・時間・容量】 | monoblog )

f:id:feb_march:20200409211948j:plain
やり方イメージ

slackアプリの作成

まずSlackアプリの作成から始めます。

api.slack.com

このページのCreate New Appというボタンを押します。

f:id:feb_march:20200409212239p:plain

するとこのような画面が出てくるので、作りたいアプリの名前と、そのアプリをどのワークスペースに紐づけるかを設定します。ブラウザでログインしていないワークスペースは選択できるワークスペースに出てこないので、Sign in to another workspaceというところから目的のワークスペースにログインしてください。これでアプリの作成は終わりです。

アプリの権限の設定

次に作成したアプリで、投稿に必要な設定をします。左側のメニューから、OAuth&Permissionを開いてください。その中のScopeの設定で、Bot Token Scopesの設定を行います。この設定は、このアプリに与える権限を設定します。

f:id:feb_march:20200409213120p:plain

いま、channels:read、chat:write、chat:write.publicという3個のスコープを設定しました。chat:write.publicに関しては、このbotがメンバーでないチャンネルであっても投稿することを許可します。このアプリが投稿できるチャンネルが絞られているのであれば、chat:write.publicのスコープは必要ありません。

では、OAuth&Permissionsの一番上のほうにある、Install App to WorkSpaceを押してください。今設定したスコープに応じて、権限が要求されるので、問題がなければ許可して進んでください。

インストールが終わると、Bot User OAuth Access Tokenが表示されます。このトークンが投稿するのに必要なので、どこかにメモしておいてください。他の画面に映ってしまっても、この画面にくればいつでも確認することができるので安心してください。

インストールできたら、投稿したいチャンネルにアプリを追加してください。chat:write.publicのスコープを追加していなかった場合、アプリは自分が追加されていないチャンネルには投稿ができないので、投稿ができません。

curlで投稿するテスト

とりあえずcurlコマンドで投稿できるかやってみましょう。ターミナルから、下のコードを実行してください。

url -X POST -d "Content-Type: application/json" -d "token=TOKEN" -d "channel=CHANNELNAME" -d "text=hello" https://slack.com/api/chat.postMessage

TOKENをさきほど取得したBot User OAuth Access Tokenに置き換えてください。CHANNELNAMEは投稿したいチャンネルの名前です。チャンネル名の前の#はあってもなくても大丈夫です。 うまくいっていれば次のように投稿ができます。

f:id:feb_march:20200409215001p:plain

なお、Windowsの場合はcurlコマンドがデフォルトで入っていないそうなので、試すにはインストールが必要になります。面倒だったらここは飛ばして、次パートでGASからの投稿をしてしまうのでいいでしょう。

GASを使って定期的に実行する

Google Apps Scriptを使って、定期的に実行することができるようにしましょう。GASはGoogleのサーバーで実行され、サーバーを自分で準備したりすることなく、手軽に実行環境を用意することができます。 まずGoogleDriveにアクセスして、適当なところで右クリックしてその他からGoogle Apps Scriptを選択してください。

まずトークンを設定します。ファイル->プロジェクトのプロパティ->スクリプトのプロパティにアクセスして、tokenという名前でアプリのトークンを入力してください。 エディタを次のように編集します。MESSAGEを自分が投稿したい文章に、CHANNEL NAMEを投稿したいチャンネル名に変更してください。

var token = PropertiesService.getScriptProperties().getProperty("token")
var text = "MESSAGE"
var channel = "CHANNEL NAME"
function post_to_slack() {
  post(text,  channel)
}

function post(text, channel){  
  var data = {
    "text":text,
    "channel":channel
  }
  var options = {
    "method":"post",
    "contentType":"application/json",
    "headers":{"Authorization":"Bearer "+token},
    "payload":JSON.stringify(data)
  }
  var ret = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage",options)
  Logger.log(ret)
}

できたら、上のタブから実行→関数を実行→post_to_slackを選択して、このスクリプトを実行してください。slackに投稿できたら大丈夫です。 次に、定期的に実行させるために、トリガーの設定を行います。上のタブから編集→現在のプロジェクトのトリガーを選択してください。

f:id:feb_march:20200409220822p:plain
GASのトリガーの設定
このように設定すると、毎日午後7~8時の間にコードが実行されるようになります。

おまけ: Pythonからの投稿例

Pythonから投稿する場合のコード例です。

import requests
import json

d = {}
d["text"] = "Hello"
d["channel"] = "CHANNEL NAME"

token = "your token"
h={}
h["Authorization"] = "Bearer "+token
h["contentType"] = "application/json"

r = requests.post("https://slack.com/api/chat.postMessage", data=d, headers=h)

 

VSCodeでC++のコードのコンパイルがうまくいかない

C++のコードをコンパイルするタスクが突然動かなくなってしまい、調べても解決方法がでてこなかったので備忘録です。

状況

昨日まで使っていた下のようなタスクが動かなくなりました。Macを使用してます。

"tasks": [
        {
            "label": "gnu_build",
            "type": "shell",
            "command": "/usr/local/bin/g++-9",
            "args": [
                "-std=gnu++1y",
                "-Wall",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}.exe",
                "${file}"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": [
                "$gcc"
            ]
        }
    ]

/usr/local/bin/g++-9 -std=gnu++1y -Wall -o "作成される実行ファイルのフルパス" "コンパイルしたいファイルのフルパス"をシェルで実行するタスク。
テスト用のスクリプトtest.cppはこの通り。

#include <iostream>

int main()
{
    std::cout << "Hello" << std::endl;
    return 0;
}

タスクをVSCode上で実行すると、エラーメッセージ

collect2: fatal error: cannot find 'ld'
compilation terminated.
The terminal process terminated with exit code: 1

/usr/local/bin/g++-9 -std=gnu++1y -Wall -o "作成される実行ファイルのフルパス" "コンパイルしたいファイルのフルパス"をターミナルを開いて実行するとコンパイルできた。 ターミナルで次のように実行。

$ which ld
/usr/bin/ld

VSCodeでのパスが問題っぽい。

Code->Preferences->Settingsをひらく。Userのタブからsetting.jsonを適当なところから開く(デフォルトのままなら多分~/Library/Application Support/Code/User/settings.jsonになるはず)。

"terminal.integrated.inheritEnv": false

となっていた。デフォルトはtrueなので、設定をうっかり変えてしまっていたらしい。ここをtrueにしたらコンパイルできるようになった。
ちなみにこの設定項目についての説明は
Whether new shells should inherit their environment from VS Code. This is not supported on Windows
となっている。これだけだとよくわからないが、下のリンクで説明してくれている。
日本語メモ:June 2019 (version 1.36) / Visual Studio Code Updates - やまだのブログ

itertoolsを使って列挙した場合に、要素のインデックスを取得する方法

itertoolsを使って組み合わせ列挙などを行う場合で、同時に取り出された要素のそれぞれのリスト内でのインデックスも取得したい場合の方法です。

まずenumerateを組み合わせてやるとこうなります。

import itertools

l1 = ["a", "b", "c"]
l2 = ["d", "e", "f"]

for (i1, v1), (i2,v2) in itertools.product(enumerate(l1), enumerate(l2)):
    print("index1:",i1, "value1:", v1, "index2:",i2, "value2:", v2)

# 出力
# index1: 0 value1: a index2: 0 value2: d
# index1: 0 value1: a index2: 1 value2: e
# index1: 0 value1: a index2: 2 value2: f
# index1: 1 value1: b index2: 0 value2: d
# index1: 1 value1: b index2: 1 value2: e
# index1: 1 value1: b index2: 2 value2: f
# index1: 2 value1: c index2: 0 value2: d
# index1: 2 value1: c index2: 1 value2: e
# index1: 2 value1: c index2: 2 value2: f

enumerateで生成したタプルを変数に展開するため、(i1,v1)のように囲む必要があるのに注意してください。

forループを下のように書いても同じようになります。インデックスの組み合わせを作成して、値を取得します。

for i1, i2 in itertools.product(range(len(l1)), range(len(l2))):
    v1 = l1[i1]
    v2 = l2[i2]
    print("index1:",i1, "value1:", v1, "index2:",i2, "value2:", v2)

また、加えてforループが何回回ったかを取得するなら次のようにかけます。展開する変数がごちゃごちゃで嫌なので、シンプルに書ける方法ご存知だったら教えてください…

for i, ((i1, v1), (i2,v2)) in enumerate(itertools.product(enumerate(l1), enumerate(l2))):
    print(i, "index1:",i1, "value1:",v1, "index2:", i2, "value2:", v2)

#  出力
# 0 index1: 0 value1: a index2: 0 value2: d
# 1 index1: 0 value1: a index2: 1 value2: e
# 2 index1: 0 value1: a index2: 2 value2: f
# 3 index1: 1 value1: b index2: 0 value2: d
# 4 index1: 1 value1: b index2: 1 value2: e
# 5 index1: 1 value1: b index2: 2 value2: f
# 6 index1: 2 value1: c index2: 0 value2: d
# 7 index1: 2 value1: c index2: 1 value2: e
# 8 index1: 2 value1: c index2: 2 value2: f