Linuxファイルの操作は要注意
MicrosoftはBUW(Bash on Ubuntu on Windows)使用時、「いかなる状況下でもWindowsアプリケーションやツール、スクリプト、コンソールからLinuxファイルの変更をしてはならない」と、公式ブログで警告を発している。ここで言う"Linuxファイル"とは、「%LOCALAPPDATA%\lxss」フォルダーから始まるrootディレクトリ下を指す。
その理由としてMicrosoftは、WSLがLinuxファイルの権限や所有権、タイムスタンプなどで構成されたメタデータを検出できない場合、ファイルが破損していると判断してしまう。NTFSは独自の拡張属性を通じてメタデータを管理するため、この間に競合が発生し、場合によってはWSLの再インストールが必要となるそうだ。
本連載のようにWindows 10を基盤にBashのシェルスクリプトを実行して、Windows上のファイルを操作する際は、「/mnt/c/……」とマウントしたディレクトリ経由であれば問題はない。逆にWindows 10のバッチファイルやPowerShellスクリプトから、Linux上のホームディレクトリにアクセスするのは避けた方がよさそうだ。
getoptsを活用して動作を拡張する
さて、第21回から紹介しているシェルスクリプトでは、Bashの内部関数であるgetoptsを使用し、コマンドラインオプションを取得して、その動作を変更してきた。前回のシェルスクリプトでは、PowerShellのコマンドレット「Get-EventLog」を使用し、「System」といったイベントログ名を指定してから、コマンドレット「Select-Object」で特定のオブジェクトを取り出している。ここで思いつくのが、getoptsからイベントログ名やオブジェクト名を指定できないかという点だ。今回は観点からシェルスクリプトを作成している。いつもと同じく実行権限を与えてからお試し頂きたい。
#!/bin/bash
CMDNAME=`basename $0`
function usage() {
echo "Usage: $CMDNAME [-l] [-s Application|Security|System] [-t EntryType|Source|Message]" 1>&2
exit 0
}
while getopts :ls:t: Option
do
case $Option in
l )
Flag_L="TRUE" ;;
s )
Flag_S="TRUE"
LogName=$OPTARG ;;
t )
Target=$OPTARG ;;
\?* )
usage ;;
esac
done
shift $((OPTIND - 1))
which powershell.exe >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Powershell.exeが使えません."
exit 1
fi
if [ "$Flag_L" = "TRUE" ]; then
Count_Critical=0
Count_Alert=0
Count_Details=0
Count_Error=0
Count_Info=0
orig_ifs=$IFS
IFS=$'\n'
for Line in `powershell.exe ./Begin.ps1 EntryType`; do
x=`echo ${Line} | grep -v -e '^\s*$' -e '^EntryType' -e '^------'`
case $x in
*Critical* )
Count_Critical=$((Count_Critical+1)) ;;
*Warning* )
Count_Alert=$((Count_Alert+1)) ;;
*Verbose* )
Count_Details=$((Count_Details+1)) ;;
*Error* )
Count_Error=$((Count_Error+1)) ;;
*Information* )
Count_Info=$((Count_Info+1)) ;;
esac
done
IFS=$orig_ifs
echo 重大レベルは $Count_Critical 件
echo 警告レベルは $Count_Alert 件
echo 詳細レベルは $Count_Details 件
echo エラーレベル $Count_Error 件
echo 情報レベルは $Count_Info 件
fi
if [ "$Flag_S" = "TRUE" ]; then
TMPFILE=/mnt/c/Users/kaz/Desktop/$$tmp.txt
Count=0
if [[ "$LogName" =~ Application$|Security$|System$ ]]; then
if [[ "$Target" =~ EntryType$|Source$|Message$ ]]; then
for Line in `powershell.exe -NoProfile -ExecutionPolicy Unrestricted -Command "& { Get-EventLog $LogName -newest 100 | Select-Object $Target}"`; do
x=`echo ${Line} | grep -v -e '^\s*$' -e '^Source' -e '^------'`
if [ -n "$x" ]; then
Array=(`echo $x` "${Array[@]}")
Count=$((Count+1))
fi
done
orig_ifs=$IFS
IFS=$'\n'
Array2=($(echo "${Array[*]}" | sort))
IFS=$orig_ifs
for v in "${Array2[@]}"; do
echo $v >> $TMPFILE
done
uniq -c $TMPFILE
rm $TMPFILE
else
usage
fi
else
usage
fi
fi
先ほど述べたように今回はオプション周りを変更している。そのため、5~8行目のメッセージ出力の内容を変更し、10~23行目では新たに「-t」を追加するため、getoptsのオプションを「:ls:t:」に変更した。末尾にコロンを付けることで、引数を変数「OPTARG」に格納するため、それを別の変数に代入している。
この処理を利用するのが66~95行目のif文だ。新たに70~71行目で引数の文字列が正しいか否かを正規表現で判断するため、2重のブラケットで囲んでいる。筆者の勉強不足のため、正規表現とAND処理を同時に実行できなかったため、このようなコードになってしまった。なお、引数が想定したものではない場合、89~91行目もしくは92~94行目でエラー処理を行っている。
さらに今回はBUWからPowerShellを呼び出せない環境でシェルスクリプトを実行した際はエラーとするため、27~31行目のコードを追加した。コマンド「which」でpowershell.exeが存在するか確認し、エラーコードが「1」の場合はエラーメッセージを出力して終了させている。
それ以外の内容は以前のシェルスクリプトと同じだ。今回はイベントログ名やオブジェクト名を決め打ちしているが、用途に合わせて70~71行目のif文と10~23のgetoptsによる処理を変更すると、現場の用途に合わせて使いやすくなるだろう。
阿久津良和(Cactus)