どこでも見れるメモ帳

とあるSEの備忘録。何かあれば気軽にコメントください〜

ローカルのシェルスクリプトをリモート実行する

はじめに

f:id:ni66ling:20160611155037p:plain

ローカルのスクリプトをリモートで実行したいけどどうやれば?という話.
どうやるのが定番なのか?は結局わかっていないが,一応できるにはできたのでメモ.

やりかた

sshコマンドにパイプでローカルのスクリプトを引き渡す.*1

$ cat [local_script.sh] | ssh [user]@[host] bash

補足: ローカルにおける変数をリモートスクリプト内で使うには

プロセス置換*2を用いる.

$ param="local"
$ cat <(echo -e "param=\"${param}\"\n") [local_script.sh] | ssh [user]@[host] bash

ただし,懸念点として,リモート実行するスクリプト内容が分散し,見通しが悪くなってしまう問題がある.
もっといい方法をご存知の方がいらっしゃったらお教えいただけると嬉しいです.

補足: cronやjenkinsからの自動実行するには

パスワード認証を自動突破するためにexpectを用いる.*3
ソースコードで示すと以下のとおり.

#!/bin/sh
# ローカルのスクリプト(リモート実行する)
remote_command_file="./local_script.sh"

# expectでSSH接続 ([hoge]は適宜読み替えて)
expect -c "
  set timeout -1
  log_file $log_file
  spawn bash -c \"cat $remote_command_file | ssh [user]@[host] bash\"
  expect \"password\" ; send \"[password]\n\"
  expect eof  # :interactとしないこと
  exit        # :interactとしないこと
"

expectに関する注意として,interactとはせず,expect eof; exitとすること.
ターミナルから実行する場合はinteractで良いが,cronやjenkinsから実行する場合はexpect eof; exitとする必要がある.*4*5

*1:注意点として,パイプで渡されるsshでは,bashを引数にすること.bashを引数にしなくても実行はできるが,標準エラー「Pseudo-terminal will not be allocated because stdin is not a terminal.」が出力されてしまう.また,さらに補足すると,この標準エラーは多段接続時に「-t」パラメータを指定しなかった場合にも出力される.

*2:http://sechiro.hatenablog.com/entries/2013/08/15

*3:公開鍵認証かつパスワードなしの設定であれば,expectを使う必要はない.$ ssh -i [鍵path] [user]@[host] bash, もしくは, sshのconfigファイルを読ませるなど

*4:http://yu-write.blogspot.jp/2013/11/shell-crontabexpect.html

*5:ここでかなりハマった…

行ごとに文字数を換算する

はじめに

行ごとに文字数を計算したい状況が発生したためメモ。

やりかた

$ cat input_file | while read line; do echo $line $((`echo $line | wc -m` - 1)); done

文字数の計上は $ wc -m。これはマルチバイト文字に対応しており、日本語と英語が混ざっても、適切に文字数を計算してくれる。*1
ただし、$ wc -m では、改行文字も文字数に計上してしまう。そのため上のコードでは1文字分だけ差し引いている。

*1:補足。バイト数を計上したい場合は $ wc -c。単語数を計上したい場合は $wc -w。

before_filterをすべてスキップする


はじめに

子クラスで親クラスのbefore_filterをすべて実行させたくない状況が生じたのでメモ。

やりかた

skip_before_filter(*_process_action_callbacks.select{|filter| filter.kind == :before}.map(&:filter))

まず、skip_before_filterは、フィルタのシンボルを渡すとスキップできる。*1
フィルタのシンボルは、_process_action_callbacksから抜き出す。*2 *3
これは配列で返ってくるので、*で配列の展開をしている。*4

おまけ

子クラスのbefore_filterを先に実行し、その後に親クラスのbefore_filterを実行するには?
before_filterは、引数の順序にしたがって実行される。
よって、子の後に親を実行するには、1)親クラスのbefore_filterをすべてスキップし、2)子クラスのbefore_filterを実行し、3)親クラスのbefore_filterを実行すれば良い。
具体的には以下のとおり。

super_before_filters = _process_action_callbacks.select{|filter| filter.kind == :before}.map(&:filter)
skip_before_filter *super_before_filters
before_filter :child_before_filter, *super_before_filters

同じ考え方で、子クラスのbefore_filterを親クラスのbefore_filterの合間に実行したり、親クラスのbefore_filterを部分的に実行したりできる。

Pythonでパイプを含むコマンド実行を手軽に行う

はじめに


Pythonで「パイプを含むコマンド」を実行する方法が分かりづらかったため、手軽に行う方法を簡単にメモ。
参考: 17.1. subprocess ― サブプロセス管理

方法

以下をテンプレートとする。commandに「実行したいパイプを含むコマンド」を宣言する。
他(#1~#3)は状況に応じて書き換える。基本的にこのままで使えると思う。

import subprocess

command = "ls -la | sort -r"                  #!ここに実行したいコマンドを書く!
proc = subprocess.Popen(
    command,
    shell  = True,                            #シェル経由($ sh -c "command")で実行。
    stdin  = subprocess.PIPE,                 #1
    stdout = subprocess.PIPE,                 #2
    stderr = subprocess.PIPE)                 #3

stdout_data, stderr_data = proc.communicate() #処理実行を待つ(†1)
print stdout_data  #標準出力の確認
print stderr_data  #標準エラーの確認

#1~#3: リダイレクトをどうするか。標準エラーを標準出力として扱う設定などがここでできる。上の例だと、標準出力は標準出力、標準エラーは標準エラー、標準入力は標準入力として扱う。
†1: 別の処理を待つ方法として、proc.wait()し、proc.stdout.read()する方法があるが、これは使用すべきではない。標準出力のデータ量が多い場合、デッドロックとなり、処理が返ってこなくなる可能性がある。詳細はPython の subprocess で出力を受け取るときは communicate() を使おう

sedによる文字列操作

f:id:ni66ling:20160611160134p:plain

はじめに

sed*1便利ですよね.ちょっとした文字列操作が簡単にできます.
ただ,日常的に使わないと,コマンドとかオプションとかを覚えるのが難しいですよね.
ワンライナーな用途を前提として,どういった使い方ができるのか,整理してみました.

sedコマンドの構造

sedコマンドは,おおまかに次の3つの構造からなります.

  • A. コマンドオプション(入力ファイルを編集する?標準出力を抑制する?など)
  • B. 範囲指定(どの行について処理する?)
  • C. 文字列操作(どういう文字列操作をする?)

例えば,

$ sed -i '3,10 s/hoge/fuga/g' input.txt

であれば,

  • A. コマンドオプション:「-i」:B.C.の操作結果を入力ファイル(input.txt)に上書きする
  • B. 範囲指定:「3,10」:3〜10行目について
  • C. 文字列操作:「s/hoge/fuga/g」:hogeがあれば全てfugaに置換する

という処理となります.この例から分かるように,

$ sed [コマンドオプション] '[範囲指定] [文字列操作]' [入力ファイル]

となります.ここでは,[入力ファイル]をとっていますが,パイプからも入力として渡せます.

$ cat [入力ファイル] | sed [コマンドオプション] '[範囲指定] [文字列操作]'

では,[コマンドオプション],[範囲指定],[文字列操作]について詳しく見ていきます.

A. 代表的な[コマンドオプション]について

a. スクリプトを直接記述「-e」

$ sed -e '[範囲指定] [文字列操作]' [入力ファイル]
# 例
$ sed -e '3,10 s/hoge/fuga/g' input.txt #結果が標準出力に出力される


b. 入力ファイルを編集「-i」

$ sed -i '[範囲指定] [文字列操作]' [入力ファイル]
# 例
$ sed -i '3,10 s/hoge/fuga/g' input.txt #結果が入力ファイル(input.txt)に上書きされる


c. 標準出力を抑制「-n」

$ sed -n '[範囲指定] [文字列操作]' [入力ファイル]
# 例
$ sed -i '3,10 s/hoge/fuga/g p' input.txt #明示的に「p」した行のみ出力される


B. 代表的な[範囲指定]について

a. 行数範囲指定(◯行目〜△行目について文字列操作「◯,△」)

$ sed [コマンドオプション] '[開始行数],[終了行数] [文字列操作]' [入力ファイル]
# 例
$ sed -e '3,10 s/hoge/fuga/g' input.txt  #3〜10行目
$ sed -e '3,10! s/hoge/fuga/g' input.txt #3〜10行目以外.!をつけると否定


b. 行数範囲指定(◯行目〜◯+△行目について文字列操作「◯,+△」)

$ sed [コマンドオプション] '[開始行数],+[行数] [文字列操作]' [入力ファイル]
# 例
$ sed -e '3,+7 s/hoge/fuga/g' input.txt  #3〜10(=3+7)行目
$ sed -e '3,+7! s/hoge/fuga/g' input.txt #3〜10(=3+7)行目以外.!をつけると否定


c. 正規表現マッチ行指定(正規表現マッチした行について文字列操作「/regex/」)

$ sed [コマンドオプション] '/[正規表現]/ [文字列操作]' [入力ファイル]
# 例
$ sed -e '/p.*o/ s/hoge/fuga/g' input.txt  #正規表現/p.*o/にマッチした行
$ sed -e '/p.*o/! s/hoge/fuga/g' input.txt #正規表現/p.*o/にマッチした行以外.!をつけると否定


d. 正規表現マッチ行範囲指定(正規表現マッチした◯行目〜正規表現マッチした◯行目について文字列操作「/regex/,/regex/」)

$ sed [コマンドオプション] '/[開始の正規表現]/,/[終了の正規表現]/ [文字列操作]' [入力ファイル]
# 例
$ sed -e '/p.*o/,/h.*a/ s/hoge/fuga/g' input.txt  #正規表現/p.*o/〜正規表現/h.*a/にマッチした行
$ sed -e '/p.*o/,/h.*a/! s/hoge/fuga/g' input.txt #正規表現/p.*o/〜正規表現/h.*a/にマッチした行以外.!をつけると否定


e. 行飛ばし指定「n」

$ sed [コマンドオプション] '[範囲指定]n [文字列操作]' [入力ファイル]
# 例
$ sed -e '3n s/hoge/fuga/g' input.txt          #3行目飛ばし
$ sed -e 'n s/hoge/fuga/g' input.txt           #全ての行について1行飛ばし
$ sed -e '{n;n;n} s/hoge/fuga/g' input.txt     #全ての行について3行飛ばし.{}で括るとグルーピングして一括実行
$ sed -e '3,20{n;n;n} s/hoge/guga/g' input.txt #3〜20行目について3行飛ばし.{}で括るとグルーピングして一括実行


f. 未指定「」

$ sed [コマンドオプション] '[文字列操作]' [入力ファイル]
# 例
$ sed -e 's/hoge/fuga/g' input.txt #未指定=全ての行


C. 代表的な[文字列操作]について

a. 文字列の全置換(◯を△に全置換「s/◯/△/g」)

$ sed [コマンドオプション] '[範囲指定] s/[置換対象文字列]/[置換後文字列]/g' [入力ファイル]
# 例
$ sed -e 's/hoge/fuga/g' input.txt             #マッチしたhogeをすべてfugaに置換
$ sed -e 's/\(hoge\)\(fuga\)/\2\1/g' input.txt #hogeとfugaを順序入れ替え.[置換対象文字列]で\(\)で括ると,
                                               #[置換後文字列]にて左から「\1」,「\2」,..で参照できる
$ sed -e 's/hoge/&&/g' input.txt               #hogeをhogehogeに置換.[置換後文字列]にて「&」でマッチした内容を
                                               #参照できる


b. 文字列の指定置換(◯番目にマッチした△を❏に置換「s/△/❏/◯」)

$ sed [コマンドオプション] '[範囲指定] s/[置換対象文字列]/[置換後文字列]/[置換対象]' [入力ファイル]
# 例
$ sed -e 's/hoge/fuga/3' input.txt #3番目にマッチしたhogeをfugaに置換


c. 行削除「d」

$ sed [コマンドオプション] '[範囲指定] d'
# 例
$ sed -e '/hoge/ d' #hogeが含まれる行を削除


d. 出力「p」

$ sed [コマンドオプション] '[範囲指定] p'
# 例
$ sed -ne '/hoge/ p' #hogeが含まれる行だけを標準出力


より高度な操作

a. 複数の文字列操作(コマンド1とコマンド2の同時実行:「コマンド1;コマンド2」)

$ sed [コマンドオプション] '[範囲指定1] [文字列操作1];[範囲指定2] [文字列操作2]]'
# 例
$ sed -e '/hoge/d; /foo/d; s/fuga/piyo/g' #hogeとfooが含まれる行を削除.fugaをpiyoに全置換


b. ネスト化した文字列操作(範囲指定◯に対して文字列操作△と文字列操作❏「◯{△;❏}」)

# {}内のコマンドを,その左に対して実行する
# 例
$ sed -e '/hoge/,/fuga/{/piyo/d;/hogera/d}' #正規表現/hoge/〜正規表現/fuga/にマッチした行について,
                                            #正規表現/piyo/と正規表現/hogera/にマッチした行を削除

おわりに

いかがでしたでしょうか?
sedは使えるようになると強力な武器になると思います.
しかし,使うことを敬遠してしまうと,すぐにオプションを忘れてしまいがちですよね…
日常的に使って,どんどん使えることを増やせるように心がけたいと思います^^

*1:Stream EDitor