WindowsバッチをC Shell化した際のポイントをまとめてみた

## はじめに
今回業務内で既存のWindowsバッチをLinuxに移植する必要がありました。
Windowsバッチの基本をおさえながら、移植の際に調べたポイントをまとめていきます。
なお、要件により今回は"C Shell"用に書き換えました。

## 概要
WindowsバッチとC Shellでは、コマンドや変数の表現、制御構造などに違いがあります。例えば、変数の展開や条件分岐などが異なりますので、これらの違いを理解してスクリプトを移植することが必要でした。
今回は、移植時のポイントをいくつかまとめてみました。

### そもそもC Shellとは
C Shellは、C言語の構文や機能をシェルスクリプトに取り入れたもので、高度な制御構造や変数の扱いが可能です。これにより、複雑な処理や大規模なプロジェクトに適しています。C Shellはオープンソースであり、多くのプラットフォームで利用できます。

## 移植時のポイント

### 改行コードの違い
WindowsバッチとC Shellの違いというよりは、OSの違いによるものです。
一般的に、Windowsでは\<CR\>\<LF\>（\r\n）、Linuxでは\<LF\>（\n）を使います。

### シバン行（Shebang行）の記載
シバン行（Shebang行）は、UNIXおよびLinuxシステム上でスクリプトファイルを実行する際に使用される特別な行です。C Shellの場合は以下のような形式で記載します。
```shell
#!/bin/csh
```

### 引数の取得方法
Windowsバッチでは、引数の数が10以上になると、<code>%10</code>以降の引数は <code>%1</code> から <code>%9</code> までの引数と同じようには取得できません。そのため、shift コマンドを使用して引数をずらしていく必要があります。
```bat
@echo off
echo 1st argument: %1
echo 2nd argument: %2
echo 9th argument: %9
@rem shiftでずらして取得する
shift
echo 10th argument: %9
shift
echo 11th argument: %9
```

C Shellでは、引数を <code>$1, $2, ..., $9, $10, $11, ... </code>として直接アクセスできます。引数が10以上でも、特別な操作は必要ありません。

```shell
#!/bin/csh

echo 1st argument: $1
echo 2nd argument: $2
echo 10th argument and beyond: $10 $11
```

ちなみに、C Shellでもwindowsと同様に shift を使用することができます。
```shell
#!/bin/csh

echo 9th argument: $9
# shiftでずらして取得する
shift
echo 10th argument: $9
shift
echo 11th argument: $9
```

### 空白を含む値を環境変数に設定する場合

Windowsバッチでは環境変数の設定に set コマンドを使用します。
また、 % を使用して変数名を括り、変数を取得できます。

この時、環境変数の設定値に空白が含まれている場合は、変数設定時の引数 または 使用時の変数を <code>""</code> で括ります。
変数設定時の引数を <code>""</code> で括ると、変数に設定される値にも <code>""</code> が付与されます。
なので、設定時と使用時の両方で <code>""</code>で括ると２重に " がついてしまいエラーになってしまいます。

```bat spaceInEnv.bat
@echo off
@echo OK: 設定時に括る
set MY_PATH_1="c:\qiita advent calendar2023\"
echo dir %MY_PATH_1% /B
dir %MY_PATH_1% /B

@echo .
@echo OK: 使用時に括る
set MY_PATH_2=c:\qiita advent calendar2023\
echo dir "%MY_PATH_2%" /B
dir "%MY_PATH_2%" /B

@echo .
@echo NG: 設定時にも使用時にも括る
set MY_PATH_3="c:\qiita advent calendar2023\"
echo dir "%MY_PATH_3%" /B
dir "%MY_PATH_3%" /B
```

```bat spaceInEnv.bat実行結果
C:\temp>spaceInEnv.bat
OK: 設定時に括る
dir "c:\qiita advent calendar2023\" /B
いいねボタンを押してね.md
.
OK: 使用時に括る
dir "c:\qiita advent calendar2023\" /B
いいねボタンを押してね.md
.
NG: 設定時にも使用時にも括る
dir ""c:\qiita advent calendar2023\"" /B
指定されたファイルが見つかりません。
```

C Shellでは環境変数の設定に setenv コマンドを使用します。
また、環境変数の値を取得するには、$ で変数名を指定します。

この時、<strong>環境変数の設定時の引数に空白が含まれるとエラーになります</strong>。
対処法として、変数設定時の引数を <code>""</code>で括る必要があります。
変数設定時の引数を <code>""</code>で括っても、<strong>変数に設定される値には <code>""</code>がつきません</strong>。
なので変数を別のコマンドの引数に使用する場合は、変数を <code>""</code>で括る必要があります。

```shell spaceInEnv01.sh
#!/bin/csh
echo NG: 設定時に括る
setenv MY_PATH_1 "/mnt/c/qiita advent calendar2023/"
echo ls $MY_PATH_1
ls $MY_PATH_1
echo .
echo NG: 使用時に括る
setenv MY_PATH_2 /mnt/c/qiita advent calendar2023/
echo ls $MY_PATH_2
ls "$MY_PATH_2"
```

```shell spaceInEnv01.sh 実行結果
root@machine01:~# csh spaceInEnv01.sh
NG: 設定時に括る
ls /mnt/c/qiita advent calendar2023/
ls: cannot access '/mnt/c/qiita': No such file or directory 
ls: cannot access 'advent': No such file or directory       
ls: cannot access 'calendar2023/': No such file or directory
.
NG: 使用時に括る
setenv: Too many arguments.
```

```shell spaceInEnv02.sh
#!/bin/csh
echo OK: 設定時にも使用時にも括る
setenv MY_PATH_3 "/mnt/c/qiita advent calendar2023/"
echo ls "$MY_PATH_3"
ls "$MY_PATH_3"
```

```shell spaceInEnv02.sh 実行結果
root@machine01:~# csh spaceInEnv02.sh
OK: 設定時にも使用時にも括る
ls "/mnt/c/qiita advent calendar2023/"
'いいねボタンを押してね.md'
```

### #を変数に設定する場合
前述のとおり空白を含む値を変数にセットする場合は、<code>""</code>で括ることを意識していたのですが、空白を含まない場合でも注意が必要です。
<code>#</code>を環境変数に設定する場合、<code>#</code>はコメントとして扱われてしまうため<code>""</code>で囲わない場合もエラーになりません。
（私はここに<code>""</code>を付け忘れていたので、後続処理で発生したエラー原因を特定するのに苦労しました。）

```shell sharpInEnv.sh
#!/bin/csh

# OK
setenv FILE_COMMENT_SYMBOL "#"
echo The value of FILE_COMMENT_SYMBOL is $FILE_COMMENT_SYMBOL

# NG（エラーにはならない）
setenv FILE_COMMENT_SYMBOL #
echo The value of FILE_COMMENT_SYMBOL is $FILE_COMMENT_SYMBOL
```

```shell sharpInEnv.sh実行結果
root@machine01:~# csh sharpInEnv.sh
The value of FILE_COMMENT_SYMBOL is #
The value of FILE_COMMENT_SYMBOL is  
```

### 条件分岐とラベル
Windowsバッチでは、if 文に加え、else if や else を使用して条件分岐を行います。
また、goto コマンドを使って指定されたラベルにジャンプします。
ラベルは「コロン : 」で<strong>始まり</strong>、処理ブロックを定義します。

```bat
@echo off
set TYP=%1
echo The value of TYP =  %TYP%
if "%TYP%" == "1"	goto :X
if "%TYP%" == "2"	goto :Y

:X
set KEY="X"
goto :nxt

:Y
set KEY="Y"
goto :nxt

:nxt
set COMN_VAL=COMN_VAL

echo The value of KEY is %KEY%
echo The value of COMN_VAL is %COMN_VAL% 
```

C Shellでは、if 文に加え、then や else if、endif を使って条件ブロックを構築します。
goto コマンドを使用してラベルにジャンプしますが、<strong>goto は省略してもラベルにジャンプできます</strong>。
ラベルは「コロン : 」を<strong>後ろ</strong> につけます。

```shell
#!/bin/csh

setenv TYP $1
if "$TYP" == "1" then
    goto X
else if "$TYP" == "2" then
    goto Y
endif

X:
setenv KEY X
goto nxt

Y:
setenv KEY Y
# gotoを省略したケース
nxt

nxt:
setenv COMN_VAL COMN_VAL

echo The value of KEY is $KEY
echo The value of COMN_VAL is $COMN_VAL
```

## まとめ
以上、移植の際に調べたポイントをまとめてみました。
C Shellのみならず、Windowsバッチについても理解が深まり良い経験になったと思います。
WindowsバッチやC Shellを実装することに対する抵抗もなくなったので、今後は身の回りの業務を一部自動化してみるなど、アウトプットする場を自分で作って行きたいと思います。