技術メモのかけら

内容はもとより調べたことすら忘れてしまうので個人的な技術メモを残しているブログです。とにかく短く、結論だけ書いていきます。

Bashで変数展開やコマンド置換しないでヒアドキュメントしたい

クリップボードの内容をSSHで接続した先のファイルに保存したい場合にヒアドキュメントを良く使うのですが、
↓こういう書き方をすると、""で囲った時と同じように変数展開されたりコマンド置換されてしまい、シェルスクリプトのコピーなどでは都合が悪いです。

<実行コマンド>
cat<<EOS
$HOME
EOS

<出力結果>
/home/taro

そういうときは ヒアドキュメントの区切り文字(何て呼ぶんだろう)を'EOS'のようにシングルクウォートで囲めば展開されなくなります。

<実行コマンド>
cat<<'EOS'
$HOME
EOS

<出力結果>
$HOME

ファイルからパターンにマッチした範囲の行を取得する

sedの場合はsed -n '/開始パターン/,/終了パターン/p' で、開始パターンにヒットした行から終了パターンの行の範囲を取得できる。

$ seq 10 | sed -n '/5/,/8/p'
5
6
7
8

awkawk '/開始パターン/,/終了パターン/' で書けるのでこっちの方が簡単である。

$ seq 10 | awk '/5/,/8/'
5
6
7
8

ログの確認に使うと捗りそうです。

既に存在しないディレクトリでコマンドを実行したときのエラー

ターミナルで作業中に、カレントディレクトリが他プロセスにより削除された後でコマンドを打つとエラーになることがあります。
冷静にメッセージを見れば察しはつくのですが、余裕がない時に起きるとちょっとビックリする。

  • pwdコマンドは削除済みのフォルダであっても特にエラーにならない
$ ls /tmp
#/tmp以下にディレクトリは存在しない

$ pwd
/tmp/work
$ mkdir hoge
mkdir: cannot create directory `hoge': No such file or directory

$ touch hoge
touch: cannot touch `hoge': No such file or directory
$ psql
could not identify current directory: No such file or directory
could not identify current directory: No such file or directory
/usr/bin/psql: could not find own program executable
$ java version
Error occurred during initialization of VM
java.lang.Error: Properties init: Could not determine current working directory.
        at java.lang.System.initProperties(Native Method)
        at java.lang.System.initializeSystemClass(System.java:1166)
  • npmコマンド
npm --version
path.js:1163
          cwd = process.cwd();
                        ^

Error: ENOENT: no such file or directory, uv_cwd
    at Object.resolve (path.js:1163:25)
    at Function.Module._resolveLookupPaths (module.js:408:17)
    at Function.Module._resolveFilename (module.js:480:22)
    at Function.Module._load (module.js:437:25)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at /root/.nodebrew/node/v8.1.3/lib/node_modules/npm/bin/npm-cli.js:19:21
    at Object.<anonymous> (/root/.nodebrew/node/v8.1.3/lib/node_modules/npm/bin/npm-cli.js:92:3)
    at Module._compile (module.js:569:30)
    at Object.Module._extensions..js (module.js:580:10)

pg_dumpのZオプションで作ったファイルの解凍&リストアにいつも混乱する

pg_dumpコマンドに、 -Z 9 のようにZオプションを与えるとダンプファイルがgzipで圧縮できます。

$ pg_dump -f /mydb.sql -Z 9 mydb

-F でフォーマットを指定しない場合、plain形式(要はSQLファイル)でダンプ&gzipで圧縮されるだけなんですが、なぜか私は「pg_restoreでリストアしなきゃ」という思考回路になっているようですorz 一瞬混乱した後に、gunzipして解凍しなきゃと思うのですが、今度は次の問題にハマります。

$ gunzip mydb.sql
gzip: mydb.sql: unknown suffix -- ignored

メッセージの通り、拡張子が .gz じゃないとNGってだけなんですが、これも一瞬???となります。
別に拡張子なんてなんでも良いだろと思うのですが、、、 次からは素直に.gzで出力しよう。

こんなことでハマるのは私だけと思いますが、2度もハマったのでメモしておきました。

bashのpipefailオプション

以前から set -e を使って、エラー時に実行を止めるシェルをよく書いてましたが、
パイプでコマンドを繋げた場合、最後のコマンドにしか効かないことを今更ながら知ったのでメモ。

例えば、以下のシェルはslなんてコマンドは存在しないので、そこでエラーになり止まると思いきや、、、

#!/bin/bash

set -e

sl | echo "パイプの最後のコマンド"
echo "戻り値を確認:$?" 
echo "ここには来ないはず"

後続の処理が実行されてしまいました。

$ bash /tmp/sete.sh
パイプの最後のコマンド
/tmp/sete.sh: 行 5: sl: コマンドが見つかりません
戻り値を確認:0
ここには来ないはず

パイプの最後のechoが正常終了してるので、パイプ実行直後の$?が0となり後続が実行されるようです。

次にset -eの行をset -eo pipefailに変えて実行してみます。

$ bash /tmp/sete.sh
/tmp/sete.sh: 行 5: sl: コマンドが見つかりません
パイプの最後のコマンド

今度はパイプの最後までは実行されるが、後続処理は実行されませんでした。
上記を実行直後に$?を確認すると、コマンドが見つからない旨のエラーコード127が返っているので処理が止まるようです。

今度は、2箇所でエラーになるように絶対にヒットしないgrep detarameを追加してみます。

sl | grep detarame | echo "パイプの最後のコマンド"
$ bash /tmp/sete.sh
パイプの最後のコマンド
/tmp/sete.sh: 行 5: sl: コマンドが見つかりません

こんどは実行直後の$?は1だったので、最後にエラーになったgrepコマンドの戻り値がパイプの戻り値になるようです。