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)