シェルマッチョになったため全面改稿予定
目次の閲覧など、基本的な操作をn-xxx
というコマンドにし、シェルから利用できることにします。これらのコマンドを、n-シリーズと名付けます。n-シリーズは~/bin
に置くことにします。
早く動かしたいため、n-commands sub-command
という形式は止めました。サブコマンドを使う場合、サブコマンド間で実装の共有などができるのがメリットです。
mobaはシェルスクリプトの本を読んでいないため、いい加減なコードを書きます。御免なさい。とにかく動けばOKだと割り切っています。
n-seriesの作成
前提
これらのコマンドは、小説ディレクトリの上で実行されるものとします。エラーチェックは、ほどほどにしかやりません。
ディレクトリ構成は、
$ tree -d -L 1
.
├── build
├── gnome
└── src
作成用 Creaters
n-new
touch -a
とするだけで、既存ファイルのmodification timeの更新を避けることができます。
これもコマンドにします(~/bin/n-new
)。また、小説データのルートディレクトリから使用し、番号のみを与えて使うことにします。
echo "$@" | xargs -I{} touch -a 'src/{}.txt'
n-new {0..24}
のように使えます。スクリプトの作成後は、chmod +x ~/bin/*
で実行許可を与える必要があります(x: execute)。n-new
を実行し、ファイルが作られることを確認しましょう。
stdinを受け取れるようにする
先ほどのコマンドは、引数しか使用しませんでした。標準引数も入力として扱えるようにします。
この辺り、全く『正しい』方法なのか分かりません。特にシェルは、確信を持つのが難しい言語ですね。検索では、肝心な部分はヒットしませんし 笑
if [ -p /dev/stdin ] ; then
echo "$(cat) $@"
else
echo "$@"
fi | tr ' ' '¥n' | grep -v '^$' |
while read n; do
touch -a "src/$n.txt";
done
- echo "$@" は全ての引数をスペース区切りにします。
- tr (translate) は文字の置換を行います。
- grep -v '^$' は空行を削除します。
何度も書く部分なので、以後どのファイルからも、while read 以前は省略します。
番号 -> 原稿ファイルのパス を抽象化
後でn-path
コマンドを作り、n-new
でもn-path
コマンドを使うようにします。
touch -a "$(n-path $n)"
エラーチェックを追加
飛ばして行きますね。
..
while read n; do
[[ ! n =~ ^0$|^[1-9][0-9]*$ ]] || {
echo "not given valid number: $n" 1>&2
continue
}
touch -a "$(n-path $n)"
done
echo .. 1>&2
stderr2
に出力しています。#3でも書きました。
[[]]
と=~
正規表現でnを0以上の整数とマッチさせています。(マッチしなければリスト{}
を実行)。
[[]]
は強力なテストです。man bashの329行目に書いてあります。=~
で正規表現とマッチングできます。
- 正規表現をクオーツで囲うとエラーなので注意してください(bash)。
0
を使うとあ0あ
みたいなものにも部分的にマッチする(結果、[[]]
の終了ステータスが、マッチ成功の0になる)ので、^0$
とすることで完全一致を検出します。`
- [0-9]
は
\d(d: digit)とは書けません。文字クラス
:digit:`はありますが、使いたくないですね。
[[ .. =~ // ]]
の代わりに、grepの終了ステータス$?
(マッチが無ければ1)を使うこともできます。
grepで検証する場合、出力を (/dev/null
に) 捨てる手間があります。
bashとは厄介な言語ですね。このコマンドが書ければ、他のコマンドも十分書けます。
閲覧用 Viewers
目次生成 n-table
#5でもやりました。sortはfindの直後にやってもいいです。
[ -d src/ ] || exit 1
find src/* -type f | while read f; do
printf '%2d %s\n' "$(basename "$f" .txt)" "$(sed -n 1p "$f")"
done | sort -n
ひとまず、話数が2桁の前提で整形して書きました。もしくは、多めの桁数でフォーマットしておけば安全です。
テストコマンド[]
中の表現-
については、man [
を参照してください。制御文字&&
については、man bash
を参照してさい。
文字数カウントするオプション
-c
もしくは--count
オプションが指定されていた場合、文字数も表示する処理に切り替えます。エイリアスでデフォルトにしてもいいでしょう。
目次や中間データに対して列を足していくスタイルもアリです。今は3つしかありませんが、列の組み合わせは二項分布で増えていくので。
オプションの分解(パース)は、今回は単に$1
を調べれば良いです。
if ! ( [ "$1" == '-c' ] || [ "$1" == '--count' ] ) ; then
find src/* -type f | while read f; do
printf '%2d %s\n' "$(basename "$f" .txt)" "$(sed -n 1p "$f")"
done
else
find src/* -type f | while read f; do
printf '%2d %4d %s\n' \
"$(basename "$f" .txt)" \
"$(cat "$f" | wc -m)" \
"$(sed -n 1p "$f")"
done
fi | sort -n
wcの出力を使う
wcの出力が、閲覧用に整えられているのが厄介です。頭に四つの空白がありますが、printfが数値として評価し、空白を消してくれました。
wcの出力を空白区切りと見て、フィールドを抜き出す方法もあります。awkが無難です: wc -m <file> | awk -F ' ' '{print $1}'
cutでフィールドを抜き出す場合、空のフィールドを無視しないため、5番目のフィールドにアクセスして、.. | cut -d ' ' -f 5
となります(実装によるかもしれませんが)。先に、sedで行頭の空白を消し、連続する空白を圧縮しておくと確実です。
総文字数
総文字数は、wcの出力を使うなら、wc -m src/* | tail -1
。もしくは、文字数カウント付きの目次に対し、awk '{sum+=$2; print $0} END {print " " sum " chars in total."}'
などとして得られます。
後から整形する
タブ文字\t
区切りのデータを作り、最後に空白の整形をします。awkを使うのが基本だと思います。
..
fi | sort -n | awk -F '\t' '{printf "format", paramA, paramB, .. }'
左寄せでよければcolumn -t -s '\t'
も使えます。(t: table, s: separator)
確実な配列 ensureing alignment
tab文字区切りの目次にスペースを入れて、列ごとに右寄せや左寄せにします。自動的にやってくれるコマンドは無いようです(table化するツールは? 要改稿)。
配列用のコマンド(もしくは一般的なテーブリングコマンド)を作っても良いかもしれません。案としては、データの最大値(もしくは最大桁数)を記憶しておいて、awk内のprintfで配列(整形)できます。
専用の文字数カウントを使う
空白文字をカウントしない、タイトルを文字数に含めない、などの配慮をしてくれるコマンドを使用します。(#8で作るn-count)
front matter付きのファイルにする場合を考えても、専用コマンドでカウントすべきです。
| column
の自動化
n-table() { command n-table "$@" | column ; }
パイプ先なら出力に対し| column
を行う
多分こんな感じ。要改稿。
if [ -p /dev/stdout ] ; then
cat | column
else
cat
fi
n話目の内容をコピー n-pc
pbcopy
は、stdinをpaste board(クリップボード)にコピーするコマンドです。
mobaはpc
のエイリアスを作っています。
n-pcにより、n話目をpbcopyします。n-build自体は、#8で作ります。
TODO
[[ "$1" =~ ^0$|^[1-9][0-9]*$ ]] || {
echo 'error: not given number.' 1>&2
exit 1
}
[ -f "build/$1.txt" ] || {
echo "error: number $1 doesn't exist in your build." 1>2&
exit 1
}
cat "build/$1.txt" | pbcopy
n-build関連の部分はコメントアウトしておきました。実際、コピーする度にbuildするのは、安全ですが重いです。ユーザがbuildを忘れないようにする方針で行きます。
編集用 Editors
ちょっとしたコマンドの有無で大違いです。
通し番号 -> 原稿のパス n-path
基本的な道具から作ります。スペース区切りの番号を、改行区切りのsrc/n.txt
に変えます。
while read n; do
if [[ ! "$n" =~ ^0$|^[1-9][0-9]*$ ]] ; then
echo "invalid arugument: $n" 1>&2
continue
fi
echo "src/$n.txt"
done
動作を確認します。
$ echo '-2' | n-path {-1..0*
invalid arugument: -2
invalid arugument: -1
src/0.txt
$ echo '-2' n-path {-1..0} 1>/dev/null
invalid arugument: -2
invalid arugument: -1
タイトルの変更 n-title
つまり原稿の一行目を差し替えます。n-title number title
という形で使います。
一行目を消してから入力内容を挿入する
行の削除は、sedのd
コマンドで行います。ファイルの上書きは、tee経由なら、リダ入れクションよりも確実でしょう。
[ -f "$(n-path "$1")" ] || {
echo "error: not given number: '$1'" 1>&2
exit 1
}
cat "$(n-path "$1")" |
sed '1d' |
printf "$2\n%s" "$(cat)" |
tee "$(n-path "$1")" > /dev/null
gsed -i "1s/.*/$2/g"
とか、BSD系のsedでsed -i '' "1s/.*/$2/g"
としてもいいです。
話の入れ替え n-swap
シェルコマンドには、ファイルのswap(入れ替え)が無いのですね。
[ ! -f n-path "$1" ] && [ ! -f n-path "$2" ] || {
echo 'not given two valid numbers.' 1>&2
exit 1
}
mv "$(n-path $1)"{,.cptemp}
mv "$(n-path $2)" "$(n-path $1)"
mv "$(n-path $1).cptemp" "$(n-path $2)"
要改稿: mktempコマンドを利用し、安全に入れ替える
簡単にテストしておきます。
~/Dropbox/n/narou/ms $ n-table
0 プロローグ Prologue 13
1 悪の製法 Making of Evils 14
2 タイトル 15
3 あああ 16
4 いいい 17
5 18
<省略>
$ n-swap 3 4
$ n-table
0 プロローグ Prologue 13
1 悪の製法 Making of Evils 14
2 タイトル 15
3 いいい 16
4 あああ 17
5 18
<省略>
※ターミナルではきちんと揃って表示されています。
n-edit
エディタを開くコマンドを用意します。今回はパスが空白を含まないため、シェルの単語分解に任せてvim $(n-path "$@")
とすることもできます。(Vimを使う場合)。
いくつか方法を試しましたが、『Vim: Warning: Input is not from a terminal』というエラーが出ることが多いです。厄介な仕様があるようです。
これだから、シェルは程々でいい気がします……。
BSD系のxargsだと話が早いです。
n-path "$@" | tr '\n' '\0' | xargs -0o vim
この素晴らしい回答を参考に、gxargsでも書いてみました。
n-path "$@" | gxargs -d '\n' sh -c '</dev/tty vim "$@"' zero
n-path "$@" | tr '\n' '\0' | gxargs -0 sh -c '</dev/tty vim "$@"' zero
終わり
お疲れ様でした! n-new, n-table, n-pc, n-path, n-count (現在はwc -m), n-title, n-swap, n-vim が用意できました。無敵に近づいた気がしませんか?(執筆力は上がりませんが)
同じような操作を何度も書き過ぎました。まだ改善が見込めますが、それは後のお愉しみ。