シェルとUNIXコマンドの覚え書きメモ

標準エラー出力のリダイレクト
cronなどに使うために、標準エラー出力を標準出力にリダイレクトしたい場合には、次のようにします。

command infile.txt > out.log 2>&1

dateコマンドとロギング
サーバーアプリケーションのログなどで処理時刻を出力したいときには、 dateコマンドの書式を利用して次のように書けます。

echo "処理完了:`date '+%Y/%m/%d %H:%M:%S'` " >> job.log

書式の中に空白を含む場合には、このようにシングルクォートを忘れずにつける必要がありますよ。

forループのひな型
例えば複数の名前のサービスをsystemctlで5秒置きに1つずつ停止するには次のようにします。

#! /bin/sh
services="
apache2 tomcat11 tomcat12
tomcat13 tomcat14 tomcat15
"

LOGFILE=looptest.log
cat > $LOGFILE <<EOF
EOF

for s in $services; do
  echo "systemctl stop $s" >> $LOGFILE
  /bin/systemctl stop $s >> $LOGFILE 2>&1
  sleep 5
done

shスクリプトのパーミッションを0711にしたら、所有者以外は実行できるか?
これははまりました。答はPermission Deniedで実行できません。 shやcshなどのスクリプトは、shegang行(1行目)を読み取ることで実行するプログラムを切り替えます。 つまり、実行権限だけあっても駄目で、読取り権限もないと実行できません。
これに対して、CやGo言語のソースをコンパイルして作ったバイナリの実行形式(ELF)ならば、 パーミッションが0711でも所有者以外が実行できます。

ftpを自動スクリプトで行う
ftpコマンドで対話的なやりとりをせず、全自動でファイルの送受信をするには、 次のようなシェルスクリプトを書けばOKです。
なお、以前のOSではftpの代わりに「cat | ftp〜」としていたのですが、 今の環境ではそれは動きませんでした(昔の勘違いかもしれません)。

% ftp -n "falcon.center.nsnhnkmmkk.co.jp" >> $LOGFILE 2>&1 << EOF
user sysadmin wanonano
lcd "/path/of/local/dir"
cd "path/of/remote/dir"
bin
put local_file.dat remote_file.dat
dir remote_file.dat
quit
EOF

sendmailでメールを送信する
テキストファイルの内容をメールで送信するには、

/usr/lib/sendmail -f FROMアドレス -t TOアドレス < textfile

とすればOKです。なおこのtextfileの書き出しを

Subject: メールのタイトル

メールの本文
...

とすれば、Subjectが指定できます。(To:なども同様に指定できます)

tarで固めてまた展開したらファイルのパーミッションが変わるのはなぜ?
展開時に-p(--preserve-permission)を付けていないためです。
tarで展開するとき-pを付けると、作成時の各ファイルやディレクトリのパーミッションが復元されます。 付けないとどうなるか? 展開するファイルやディレクトリのパーミッションは、展開を実行するユーザーのumaskを元に一律に揃えられます。 例えば展開ユーザーのumaskが0022ならば、展開されるファイルのパーミッションは0644に、ディレクトリは0755に揃います。
なお、rootの場合は-pが付けた時の動作がデフォルト、一般ユーザーは-pを付けない時の動作がデフォルトになります。

tarでファイルを作らずにコピー

cd /the/path/of/dir2(展開先)
tar cvf -  -C ~/the/path/of/dir1 temp | tar xf -
               ↑展開元

tarコマンドはOSによってオプションが微妙に異なることもあるので、 詳しくは、マニュアルで。

cpでディレクトリを複製する際に、ファイルの日付を更新しない
cpコマンドに-pオプションをつけると、編集時刻などを元のファイルに合わせます。

cp -pr srcdir destdir

mkdirでディレクトリを作る際に、既にあってもエラーにしない
普通にmkdirを使うと、既に存在するディレクトリに対して実行するとエラーになります。
-pオプションを付けると既に存在していてもエラーにならず、何もせず正常終了します。

mkdir -p work1

曜日によって処理を分岐する

yobi=`date +%w`
if test $yobi = 1 -o $yobi = 3 -o $yobi = 5; then
  echo 111
else
  echo 222
fi

dateコマンドの「%w」書式は、曜日を日曜日=0、月曜日=1…の整数で返します。

作成日が現在より一定期間以前・以後のファイルを抽出する

find . -mtime -2

mtimeオプションの+は指定した日数より古いファイル、-は新しいファイルの意味です。 従って上記は、カレントディレクトリにある、2日以内に編集されたファイルを表示します。
なお、算数の常識に反して、+は省略できないので大注意! +も-もつけないと、「丁度2日前に編集されたファイル」という意味になり、+をつけるのとは意味が変わってきます。

作成してから一定日を過ぎたファイルを消しこむ
例えば、ディレクトリ$LOGDIRの下にある、ファイル名が*.logに該当し、 最後の編集から14日以上過ぎたファイルを削除するには、次のようにします。「\;」がポイントです。

find $LOGDIR -name '*.log' -mtime +14 -exec /bin/rm -f {} \; >> tmp.out

なお、「-type d」とするとディレクトリを指定することができ、 /bin/rm -f の代わりに/bin/rm -rf とするとディレクトリも削除できますが、 このとき重大な注意をひとつ。findの削除条件に起点ディレクトリ (上の$LOGDIR)が該当してしまった場合、起点ディレクトリ自体が消えてしまうので要注意!

様々な不要ファイルを消す
ディレクトリを削除する場合は-pruneを付け、そのディレクトリの下を辿らないようにすると万全です。

#! /bin/sh

basedir=$1
if ! test -d ${basedir}/WEB-INF; then
  echo "Ootto! please specify webapp context root directory."
  exit 1
fi

find ${basedir} -type d -name logs -prune -exec rm -rf {} \;

rm -rf ${basedir}/work/*.*
find ${basedir} -type f -name '*20[0-9][0-9][0-9][0-9][0-9][0-9]' -exec rm {} \;
find ${basedir} -type f -name '*.bak' -exec rm {} \;
find ${basedir} -type f -name '*.ddd' -exec rm {} \;

rm -f ${basedir}/WEB-INF/lib/ojdbc*.jar \
      ${basedir}/WEB-INF/lib/orai18n*.jar \
      ${basedir}/WEB-INF/lib/nls_charset*.* \
      ${basedir}/WEB-INF/lib/classes*.*

perl -i.orig -pe '{s|/usr.*/logs|\${catalina.base}/logs|;}' ${basedir}/WEB-INF/classes/log4j.*

# end.

指定した所有者のファイルの有無を探す
一例で、/dataの下を再帰的に検索し、 所有者ユーザーが"oracle"であるファイルを一つでも見つけるとそのパスを出力して終了するには、次のようにします。

find /data -type f -user oracle -print -quit

ある文字列をファイルの内容に含むファイルを検出する
find+grepでこんなこともできます。 以下は /dataの下を再帰的に検索し、拡張子が.sqlか.SQLであるファイルの内、 内容に "DBMS_JOB" を含むファイルのパスを一覧出力します。

find /data -type f \( -name "*.sql" -o -name "*.SQL" \) -exec grep -l "DBMS_JOB" {} \;

findとcommコマンドの活用
次のような処理を考えます。

  1. ある条件を満たすファイルをfindコマンドで抽出し、file1に書き出す。
  2. file1の各ファイルを処理する。処理済のファイル名をfile2に書き出す。
  3. 次回1、2の処理を行う際には、前回処理済のファイル、つまりfile2に書かれているファイルは処理対象外にする。

find /the/path/of/basedir -name '*.txt' -mtime -7 | sort > file1.txt
comm -23 file1.txt file2.txt

commコマンドは、2つのファイルに含まれる各行を比較し、 (1)file1にのみ含まれる行、(2)file2にのみ含まれる行、(3)両方に含まれる行、 の3列に仕分けて出力します。
-23というオプションは、この内(2)と(3)を出力しない、 つまりfile1にあってfile2にはない行だけを出力する意味になります。
注意点として、file1とfile2の内容は採用しているロケールでソートされている必要があります。 そのため、必要に応じて事前にsortコマンドを掛けておきます。

作成するファイルサイズの上限を設定する
作業ミスによるdiskfullの防止策として、ulimit -f というコマンドが使えます。
例えばこのログインセッションでは100MB以上のファイルを絶対作らないぞというときは

ulimit -f 102400

と設定すればOK(単位はKB)。現在の設定値はulimit -a またはulimit -fで確認できます。
初期値はunlimited(無制限)となっていると思います。
1点注意。一般ユーザーは一度値を設定したら、値を引き上げる操作はできません。 Operation not permittedというエラーになります。 値を引き上げたいときは?簡単に言えば、端末を終了してまたログインし直せば初期値に戻ります。

ディレクトリ内に貯まっていくファイルを定期的に処理する
次のような処理を考えます。

  1. ディレクトリ内にあるファイルに何かの処理をする。
  2. 次回その処理を実行するときは、前回なかった新しいファイルだけを対象にする (前回処理時刻より最終編集時刻が新しいファイルだけを処理する)
  3. 前回あったファイルが削除されていても、何もしない。

TARGET_DIR=/the/path/of/target
find $TARGET_DIR -name '*.csv' -newer timestamp.txt | sort > target.txt
for f in `cat target.txt`
do
    echo "target: $f"
    # 何かの処理
done
touch timestamp.txt

findコマンドの-newerオプションは、指定したファイルよりも最終編集時刻が新しいファイルだけを抽出します。 (そのファイル自身は対象にしません) これを使って、前回処理した最も新しいファイル以降に作成したファイルを処理対象にすることで、 定期的なバッチ処理を実現できます。

複数ディレクトリのリストから指定日時以降に編集されたファイルを探す
最近のfindには「指定した日時よりも最終編集日時が新しい」という意味の-newermtというオプションがあるのですが、 ここでは古いOSでそれがない環境を想定して、まずその日付のダミーのファイルを作ります。

touch -t 202503010000 /home/user1/fromdate.txt

そして、このファイルよりも最終編集日時が新しい、通常ファイル(ディレクトリではない)で、 パスにworkやlogといった文字を含まないものを一覧抽出する/bin/shスクリプトの例です。

#! /bin/sh

dirs="
/data1/files
/data2/files
/data3/files
"

for dir in $dirs; do
  find "$dir" -type f -newer /home/user1/fromdate.txt | perl -ne 'print if !(/work|log|old|ddd/)'
done

findコマンドの注意(1) シンボリックリンクを辿らない
findは便利なコマンドですが幾つか落とし穴があります。 まずデフォルトではシンボリックリンクを辿らないこと。-Lを付ければ、辿るようにできます。

findコマンドの注意(2) カレントディレクトリにread権限がないと不審な警告が出る
findで他のディレクトリを起点にファイルを検索する場合でも、カレントディレクトリにread権限がないと 「find: Failed to restore initial working directory: *** : Permission denied」 という謎のエラーが起きます(検索自体は正しく行われます)。 普通のユーザーだとあまり起きませんが、sudoで他の人になって実行するときに出くわすと驚くと思います。 例えばuser1のホームでuser2にsuして検索を行う場合

cd /home/user1
sudo -u user2 find /usr/share/app1 -name '*.log'

とすると、/home/user1がPermission Deniedというエラーが起きます。

ヒアドキュメントを使ったファイル作成
ヒアドキュメントはテキストファイルを書き出すのにとても便利です。

cat > $PARFILE << EOF
file=$DESTFILE
buffer=10000000
tables=(SCOTT.EMP)
EOF

ここでよくあるミスは、「cat」の代わりに「echo」を使ってしまうことです。 これははまると結構悩みます。

よくcdするディレクトリのタイプ数を短く
よくcdコマンドで移動するディレクトリがかなり階層の深いところにある場合、なるべくタイプ数を減らして行くには? 例えばホームディレクトリから(シンボリック)リンクを張る、というような方法もありますが、 Cシェルのcdpath変数を使うと、もっと簡単にできます。 例えば、~/the/deep/dir/name1 と ~/motto/d/ee/p/dir/name2 によく移動する場合、

set cdpath = ( $HOME/the/deep/dir $HOME/motto/d/ee/p/dir )

これを.cshrcに書いておくと、カレントディレクトリがどこであっても、 「cd name1」「cd name2」だけで目的のディレクトリに行けます。

BACKSPACEキーを有効にする
下の1行をログインスクリプトに書いておきます。

stty erase ^H

bashのalias
Csh系のシェルを従来使っていて、Linuxなどでbashに初めて触って戸惑うのが、 aliasコマンドの書式が違うことです。 bashでは、エイリアスと本体の間に'='が必要です。また、起動時に後ろにパラメータを渡すことはできません。

alias ec31=/usr/local/eclipse-3.1.1/eclipse

Misc. Topics Top

(first uploaded 2002/01/05 last updated 2026/05/19, URANO398)