【Java】java.sql.ResultSetから値を読み出すときメモ

JDBCは業務アプリや軽いデータ取得ツールを作るには、低レイヤすぎて面倒くさい。 特に値を読み出すところが面倒すぎてO/Rマッパに逃げた挙句、今日まともに使い方を理解したJavaおじさん5年目がメモ。

JDBCとは

Javaの標準APIに組み込まれている、データベースに接続してSQL投げてクエリするための各種セット。

サクっと(?)試せる記事があったので使い勝手はこちら参照。 https://qiita.com/wb773/items/27dc0e77a4c8b035d6fc

登場した頃の鳴り物的なものはこのあたりかも。 https://www.atmarkit.co.jp/ait/articles/0106/26/news001.html

データ取得

ResultSetの型がガバガバ

いい加減にしろっていうぐらいJava型に対応したget~メソッドが多い

            // SQLではint型で取得
            ResultSet resultSet = statement.executeQuery("SELECT 1");

            // 下記全部それっぽい値が取れてしまう
            System.out.println(resultSet.getByte(1));
            System.out.println(resultSet.getShort(1));
            System.out.println(resultSet.getInt(1));
            System.out.println(resultSet.getFloat(1));
            System.out.println(resultSet.getBigDecimal(1));
            System.out.println(resultSet.getObject(1));

困ったことにString型も、数字っぽかったらエラーなしで数値型に変換する始末。お前はPHPか。

ちょっと古いドキュメントだが、一番型的に正解で精緻な値が取れる組み合わせは、ResultSetMetaData.getType()からとれるjava.sql.Types 列挙体と下記ドキュメントを突き合せれば良い。 https://docs.oracle.com/cd/E16338_01/java.112/b56281/datacc.htm

NULL値の取り方

オブジェクト型であれば、get~メソッドでnullが帰ってくるが、プリミティブの数値型は 0 で帰ってくる。

            ResultSet resultSet = statement.executeQuery("SELECT NULL");

            // 0でとれる
            System.out.println(resultSet.getByte(1));
            System.out.println(resultSet.getShort(1));
            System.out.println(resultSet.getInt(1));
            System.out.println(resultSet.getFloat(1));

            // nullでとれる
            System.out.println(resultSet.getString(1));
            System.out.println(resultSet.getBigDecimal(1));
            System.out.println(resultSet.getObject(1));

ただし、プリミティブでも 直前のget~の取得結果がSQL上でnullだったかをResultSet.wasNullメソッドで検知できる。

            ResultSet resultSet = statement.executeQuery("SELECT NULL");

            System.out.println(resultSet.getByte(1));
            System.out.println(resultSet.wasNull());   // true

したがって、1行のデータを丸々取りたかったら、だいたいこんな感じになる。

public static ArrayList<String> readOneRecord(ResultSet rs, ResultSetMetaData metaData, int colcount, List<ColumnProp> props, SimpleDateFormat sfdTimestamp) throws SQLException {
        ArrayList<String> list = new ArrayList<String>(colcount);
        
        for (int i=0; i<colcount; i++) {
            // @see https://docs.oracle.com/cd/E16338_01/java.112/b56281/datacc.htm
            switch (metaData.getColumnType(i+1)) {

            //getstring
            case java.sql.Types.CHAR: 
            case java.sql.Types.LONGNVARCHAR: 
            case java.sql.Types.LONGVARCHAR: 
            case java.sql.Types.VARCHAR: 
            case java.sql.Types.NCHAR: 
            case java.sql.Types.NVARCHAR: 
                String vString = rs.getString(i+1);
                list.add(vString != null ? vString : props.get(i).nullas);
                break;
            //getBigDecimal
            case java.sql.Types.DECIMAL: 
            case java.sql.Types.NUMERIC:
                BigDecimal vBigDecimal = rs.getBigDecimal(i+1);
                list.add(vBigDecimal != null ? vBigDecimal.toString() : props.get(i).nullas);
                break;
            //gettimestamp
            case java.sql.Types.TIMESTAMP:
                java.sql.Timestamp vTimestamp = rs.getTimestamp(i+1);
                list.add(vTimestamp != null ? sfdTimestamp.format(vTimestamp) : props.get(i).nullas);
                break;
            //getdate
            case java.sql.Types.DATE:
                java.sql.Date vDate = rs.getDate(i+1);
                list.add(vDate != null ? sfdTimestamp.format(vDate) : props.get(i).nullas);
                break;

                //getint
            case java.sql.Types.TINYINT: 
                byte vByte = rs.getByte(i+1);
                list.add(rs.wasNull() ? Byte.toString(vByte) : props.get(i).nullas);
                break;
            case java.sql.Types.SMALLINT: 
                short vShort = rs.getShort(i+1);
                list.add(rs.wasNull() ? Short.toString(vShort) : props.get(i).nullas);
                break;
            case java.sql.Types.INTEGER: 
                int vInt = rs.getInt(i+1);
                list.add(rs.wasNull() ? Integer.toString(vInt) : props.get(i).nullas);
                break;
            case java.sql.Types.BIGINT: 
                long vLong = rs.getLong(i+1);
                list.add(rs.wasNull() ? Long.toString(vLong) : props.get(i).nullas);
                break;

            //getbool
            case java.sql.Types.BOOLEAN:
                boolean vBoolean = rs.getBoolean(i+1);
                list.add(rs.wasNull() ? Boolean.toString(vBoolean) : props.get(i).nullas);
                break;
            //getfloat
            case java.sql.Types.FLOAT: 
            case java.sql.Types.DOUBLE: 
            case java.sql.Types.REAL:
                double vDouble = rs.getDouble(i+1);
                list.add(rs.wasNull() ? Double.toString(vDouble) : props.get(i).nullas);
                break;
            //getnull
            case java.sql.Types.NULL: 
                list.add(props.get(i).nullas);
                break;
            //rowid
            case java.sql.Types.ROWID: 
                RowId vRowId = rs.getRowId(i+1);
                list.add(vRowId != null ? vRowId.toString() : props.get(i).nullas);
                break;
            //gettime
            case java.sql.Types.TIME: 
                Time vTime = rs.getTime(i+1);
                list.add(vTime != null ? sfdTimestamp.format(vTime) : props.get(i).nullas);
                break;
         default:
                list.add(props.get(i).nullas);
                break;
            // not supported
//         case java.sql.Types.ARRAY: 
//         case java.sql.Types.BIT: 
//         case java.sql.Types.BINARY: 
//         case java.sql.Types.BLOB: 
//         case java.sql.Types.CLOB: 
//         case java.sql.Types.DATALINK: 
//         case java.sql.Types.DISTINCT: 
//         case java.sql.Types.JAVA_OBJECT: 
//         case java.sql.Types.NCLOB: 
//         case java.sql.Types.OTHER: 
//         case java.sql.Types.REF: 
//         case java.sql.Types.LONGVARBINARY: 
//         case java.sql.Types.SQLXML: 
//         case java.sql.Types.STRUCT: 
//         case java.sql.Types.VARBINARY: 
            }
        }
        
        return list;
    }

【Redshift】チューニングメモ(分散スタイル)

Redshiftをノーチュニングで運用しだしたとき、まずあたるボトルネックが「ビューやアプリからのSQLでいっぱいJOINしているクエリが返ってくるのに数十秒かかる」こと。

Indexもろくに張っていない数億レコードのJoinで普通に帰ってくるなんて驚きだよ、と言いたいがユースケースは待ってくれない。

Redshiftの再分散

AWSが提供するデータベース Amazon Redshiftは水平分散型アーキテクチャが採用されており、 複数のノード(マシン)でデータを分け、それぞれで並列計算してノードの数だけ計算が速くなる。

しかし、JOINなど複数台のサーバーがそれぞれ持っているデータを持ち合わせて実施する処理を実行する場合、 せっかく分散させたデータをわざわざ全ノードに転送して、一時的に全データが全ノードにある状態にする。(再分散)

ボトルネックの可視化

  • 問題となるクエリに対してExplainをかけ、再分散が起こる実行計画を見る。

  • クエリを実行後、システムテーブル STL_BCAST や STL_DIST から再分散にかかった時間、行数、転送バイト数をチェック

    • 実績値が見れる。かなり有用

再分散を低減させる

  • 分散スタイルのチューニングを行う
    • 分散スタイルやソートキーのチューニングは他RDBのINDEXの付与と同様、特定のSQLに対してのみチューニングできてそうでないクエリのパフォーマンスが若干犠牲になる。
  • SQLを見直す
    • JOINを分散キーを使った等結合にする。(>などの範囲で比較する演算子のみの結合は使わない。)
    • GROUP BY に分散キーを入れる
  • どうにかしてJOINを減らす
    • 先もって部品となるテーブルを集計しておく
    • 繰り返し同じように使うサブクエリのパーツは、With句で集計しておく

【Redshift】SQLの実行時間明細を取得・可視化する

RedshiftのSQL実行時間の取得方法メモ。

※Redshiftは日本時間で土曜日22:00に定期メンテナンスがあり、都度都度仕様変更が入ります。執筆時点の情報である旨ご承知おきください。

A. システムテーブルからログを取得する

1. queryidの特定

実行時間を見たいSQLのqueryidを特定する。※1

特定方法:

※1 queryidは、wlmに計画されないと発行されない。ResultCacheにひっかかったらだめ、DDLはそもそも発行なし。

2. SQLを流してほしい実行ステップのログを確認する

下記SQL

※それぞれのログが独立したステップではなく、ところどころ計測区間が重複する。

-- SQL全体のステップごとの転送バイト数などを取得
-- スライスごとの負荷は見れず、そちらを確認するには後続のクエリから確認する
SELECT 'SVL_QUERY_SUMMARY', * FROM SVL_QUERY_SUMMARYWHERE query=3420575 ORDER BY query,stm,segment,step;

-- SELECT文全体
SELECT 'STL_QUERY', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_QUERY WHERE query=3420575 ORDER BY query;
-- DELETE step全体
SELECT 'STL_DELETE', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_DELETE WHERE query=3420575 ORDER BY query,segment,step,slice;
-- INSERT step全体
SELECT 'STL_INSERT', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_INSERT WHERE query=3420575 ORDER BY query,segment,step,slice;
-- 再分散(bloadcast)ステップ
SELECT 'STL_BCAST', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_BCAST WHERE query=3420575 ORDER BY query,segment,step,slice;
-- 再分散(dist)ステップ
SELECT 'STL_DIST', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_DIST WHERE query=3420575 ORDER BY query,segment,step,slice;
-- FULL OUTER JOINステップ
SELECT 'STL_NESTLOOP', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_NESTLOOP WHERE query=3420575 ORDER BY query,segment,step,slice;
-- ハッシュJOINステップ
SELECT 'STL_HASHJOIN', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_HASHJOIN WHERE query=3420575 ORDER BY query,segment,step,slice;
-- マージJOINステップ
SELECT 'STL_MERGEJOIN', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_MERGEJOIN WHERE query=3420575 ORDER BY query,segment,step,slice;
-- ハッシュ化?(詳細不明)ステップ
SELECT 'STL_HASH', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_HASH WHERE query=3420575 ORDER BY query,segment,step,slice;
-- マージステップ
SELECT 'STL_MERGE', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_MERGE WHERE query=3420575 ORDER BY query,segment,step,slice;
-- Group by集計ステップ
SELECT 'STL_AGGR', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_AGGR WHERE query=3420575 ORDER BY query,segment,step,slice;
-- LIMIT句ステップ
SELECT 'STL_LIMIT', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_LIMIT WHERE query=3420575 ORDER BY query,segment,step,slice;
-- 列射影・選択ステップ
SELECT 'STL_PROJECT', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_PROJECT WHERE query=3420575 ORDER BY query,segment,step,slice;
-- リターンステップ(呼び出し元へのデータ返送)
SELECT 'STL_RETURN', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_RETURN WHERE query=3420575 ORDER BY query,segment,step,slice;
-- 一時データ保存ステップ
SELECT 'STL_SAVE', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_SAVE WHERE query=3420575 ORDER BY query,segment,step,slice;
-- データSCANステップ
SELECT 'STL_SCAN', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_SCAN WHERE query=3420575 ORDER BY query,segment,step,slice;
-- ソートステップ
SELECT 'STL_SORT', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_SORT WHERE query=3420575 ORDER BY query,segment,step,slice;
-- Distinctステップ
SELECT 'STL_UNIQUE', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_UNIQUE WHERE query=3420575 ORDER BY query,segment,step,slice;
-- Windows関数計算ステップ
SELECT 'STL_WINDOW', *, DATEDIFF(microsecond, starttime, endtime) as micro_secs FROM STL_WINDOW WHERE query=3420575 ORDER BY query,segment,step,slice;

B. AWSコンソールから可視化された実行計画を見る

1. queryidの特定

A.パート参照参照

2. AWSコンソールから閲覧

AWSコンソールから Redshift>クラスター>見たいクラスター>クエリ>各クエリをクリック> クエリの実行の詳細:Actual

でいい感じに可視化

はじめてVuejsアプリケーションを本格的に作った時の参考文献履歴

Vuejsのプロジェクトテンプレートを作りたい

https://qiita.com/po3rin/items/3968f825f3c86f9c4e21

.vueファイルが意味不明すぎて怖い

https://qiita.com/po3rin/items/3968f825f3c86f9c4e21

clickなどのイベント駆動で何か処理したい

https://qiita.com/Sa2Knight/items/7e22307436a1e696729d

ループ描画したい

https://jp.vuejs.org/v2/guide/list.html

フォーム入力内容を変数にバインドしたい

https://jp.vuejs.org/v2/guide/forms.html

特定の変数が true の場合だけ
のようにクラスを付与したい

https://jp.vuejs.org/v2/guide/class-and-style.html

scssを使いたい(Sass-loader)

https://qiita.com/morocco/items/b1640cfdbe87817bd6a7

または<style lang=scss">コンパイルしたときのエラーメッセージに従ってnpm install

コンポーネント分割したい

https://jp.vuejs.org/v2/guide/components-registration.html#%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0

コンポーネントから子コンポーネントのメソッドを呼びたい

https://qiita.com/MasahiroHarada/items/80c2a60a77361f944953

WIP

【Docker】初めてdocker-composeでbuild+app runまでする構成を組もうとして挫折した

Go + VueなアプリをDockerでビルド用コンテナ+稼働用コンテナ+docker-composeするまでの過程で ハマったところをつらつらとログした記事。

世には素晴らしい日本語記事がいっぱいでているが、 今更やってみたの一例として、誰かの参考になればというモチベで進行。

環境

項目 内容
ホストOS Ubuntu 16.04
Docker 17.05.0-ce
Docker Compose 1.22.0
コンテナOS alpine 3.9
Golang 1.11.5
nodejs 10.14.2
vuecli 2.9.6

とりあえずできたやつ

https://github.com/pakuyuya/dbsql-meta-viewer/tree/master/docker

  • dep ensure + go build、vuecliで生成したテンプレートをnpm run build してパッケージング、ビルドしたコンテナ自身にデプロイして動かす簡単なスクリプト

セットアップ

Install Docker/Docker-compose to host server

https://docs.docker.com/install/ http://docs.docker.jp/compose/install.html

ビルドコンテナ

流れ

  • コンテナに必要なパッケージをインストールして環境変数もいい感じに初期化したイメージを、Dockerfileからビルドする
  • ビルドするときエントリポイントにシェルを仕込んでおき、コンテナを run したら git clone -> build のあと、コンテナ内 /var/distにビルド後のモジュールを出力。
  • コンテナをrunするときに/var/distに任意のディレクトリをマウント、ビルド結果を受け取る。

ハマり1:ARGには複数の値を指定できない

ビルドするときにパラメータガンガン渡せばいーじゃんと思ったけどなんかダメだそう。

下記は×

# Dockefile
FROM alpine:3.9

ARG REPOSITORY=https://github.com/pakuyuya/dbsql-meta-viewer.git \
        ENVTARGET=production

代替策

2019/02/27

全然できた なんで悩んでたんだ・・

ハマリ2:ashで、シェルを呼んだ先のシェルを呼んだ先のシェルを呼ぶことができない

alpineの標準シェル

docker/build/Dockefile

FROM  alpine:3.9

ENV REPOSITORY=https://github.com/pakuyuya/dbsql-meta-viewer.git \
        # ... 省略 ...

ADD ./entrypoint.sh /
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

docker/build/entrypoint.sh

#!/bin/sh

git clone ${REPOSITORY} /
cd /dbsql-meta-viewer

/bin/sh ./build.sh

build.sh

#!/bin/sh

/bin/sh ./client/build.sh # ここで file not found

対策として、シェルの呼び出し階層を泣く泣く削減した。

アプリケーションコンテナ

やりたかったこと

  • build コンテナを runして、完了したらappコンテナがmountしたvolumeからアプリを受け取ってrunするはずだった

ハマり3:docker-composeの依存関係解決が貧弱

Docker Composeにはコンテナの起動順序を制御するオプションがいくつかあるが、プロセス間のシグナルな同期はサポートしていない。

  • depends_on
    • 依存するコンテナのimagesからのコンテナへマウントを待ってから、自身のコンテナのマウントを始める。runが終わるのは待たない。
  • deploy関連コンフィグ
    • update-config: delay などの微妙に使えそうな設定がある。
    • docker stack deployなどを使ったswarm modeでのみ有効になるらしく、docker-compose up や run では無視されるらしい。

結局のところビルドコンテナとアプリコンテナを泣く泣く統合することで解決したが、 シグナル制御や、volumesを共有して wait for file するのがなんだかんだいって便利なのかもしれない。

(ググって見つけた)

【Docker】Dockerfile RUNで、BASHな環境変数を展開してコマンド実行する

恐らく禁じ手

ハマった事象

DockerFileを書くとき、ARGやENVな変数をコマンド内で使うときは $ENVNAME で書くが、 少なくともDocker verions 17.05.0-ceでは、Dockerfile中の$ENVNAMEプリプロセッサ的に事前に展開してコマンド実行するようだ。

FROM alpine

ARG SOMEARG=1
ENV SOMEENV=hehehe
RUN CMDENV=foo

RUN echo \"$SOMEARG\" \"$SOMEENV\" \"$CMDENV\"

実行結果

yu@yu:~/test$ docker build --rm .
Sending build context to Docker daemon  4.096kB
Step 1/5 : FROM alpine
 ---> caf27325b298
Step 2/5 : ARG SOMEARG=1
 ---> Using cache
 ---> 1efffbdfdb5c
Step 3/5 : ENV SOMEENV hehehe
 ---> Using cache
 ---> 0b585450b6db
Step 4/5 : RUN CMDENV=foo
 ---> Using cache
 ---> 1e28e889dfdf
Step 5/5 : RUN echo \"$SOMEARG\" \"$SOMEENV\" \"$CMDENV\"
 ---> Running in f3083830fbf4
"1" "hehehe" ""
 ---> 5648473c7075
Removing intermediate container f3083830fbf4
Successfully built 5648473c7075
yu@yu:~/test$

echoした結果に注目。 RUN CMDENV=foo がきいていない。

"1" "hehehe" ""

恐らく?だが、RUN echo \"$SOMEARG\" \"$SOMEENV\" \"$CMDENV\" の箇所でコマンドラインで実行されるのはecho "1" "hehehe" "" になっているからだと思う。(Dockerのコード見る気力がないのでこれ以上は勘弁)

それでも実行したい

バージョン変わるとつぶされるかもしれないが、いったんは下記でhackできた。

RUN `echo 実行したいコマンド \$環境変数名`
yu@yu:~/test$ cat
Dockerfile        env.sh            LoadConfigScript
yu@yu:~/test$ cat Dockerfile
FROM alpine

ARG SOMEARG=1
ENV SOMEENV=hehehe

RUN CMDENV=hoo \
    && `echo echo \$SOMEARG \$SOMEENV \$CMDENV` \
    && `echo mkdir \$SOMEARG \$SOMEENV \$CMDENV` \
    && `echo ls -l \$SOMEARG  \$SOMEENV  \$CMDENV`
yu@yu:~/test$
yu@yu:~/test$ docker build --rm .
Sending build context to Docker daemon  4.096kB
Step 1/4 : FROM alpine
 ---> caf27325b298
Step 2/4 : ARG SOMEARG=1
 ---> Using cache
 ---> 1efffbdfdb5c
Step 3/4 : ENV SOMEENV hehehe
 ---> Using cache
 ---> 0b585450b6db
Step 4/4 : RUN CMDENV=hoo     && `echo echo \$SOMEARG \$SOMEENV \$CMDENV`     && `echo mkdir \$SOMEARG \$SOMEENV \$CMDENV`     && `echo ls -l \$SOMEARG  \$SOMEENV  \$CMDENV`
 ---> Running in f482c9fbcd9c
1 hehehe hoo
1:
total 0

hehehe:
total 0

hoo:
total 0
 ---> f19485b7afb9
Removing intermediate container f482c9fbcd9c
Successfully built f19485b7afb9
yu@yu:~/test$

\はDockefileのエスケープ文字だが、$に対して実施してもコマンドラインで渡しても$はただの文字として機能する。 しかし、などで一度コマンド置換すると、環境変数として評価するみたいだ。

制約

DockefileのbuildはStepごとに中間結果をキャッシュし、Dockefileを修正したときキャッシュのある部分から再ビルドする。 ・・・が、上記例でいうところのRUN CMDENV=hoo環境変数が残留になるスコープは、Step実行して評価されたbuild回でのみ。 (プロセスが一旦落ちるから?)

下記のDockerFileがあったとして、RUN FOO=var のStepがキャッシュによりスキップされた場合、Step2の出力は環境変数FOOが未設定の扱いとなる。

FROM alpine

RUN FOO=var
RUN `echo echo \$FOO`

多分Dockerの中の人たちには、どうしようもないバグとして思われているんだろうなぁ。

Linuxのbashシェルスクリプト/Windows batスクリプトの別スクリプトを呼んだときのカレントディレクトリと環境変数スコープ遷移

ハマった

Windows batスクリプトの知見

  • setlocal / endlocal しない限り、環境変数のスコープが共有される

実験

カレントディレクトリを指す環境変数 CD で例にとる

構成

|`- main.bat
 `- subdir
     `- sub.bat

main.bat

@echo off
echo [main.bat] current directory: %CD%
call .\subdir\sub.bat
echo [sub.bat] current directory: %CD%

sub.bat

@echo off
echo [sub.bat] current directory: %CD%

rem sub.batがいるディレクトリまで移動
cd %~dp0

echo [sub.bat] current directory: %CD%

実行結果

E:\work\example>main.bat
[main.bat] current directory: E:\_work\example
[sub.bat] current directory: E:\_work\example
[sub.bat] current directory: E:\_work\example\subdir
[main.bat] current directory: E:\_work\example\subdir

E:\work\example\subdir>

子供のbatでcdした結果はコマンドプロンプト含む呼び出し元にも影響。

Windows batで元のCDを残す

【1: setlocal / endlocalで環境変数のローカルスコープをリセット】

sub.bat

@echo off
setlocal
echo [sub.bat] current directory: %CD%

rem sub.batがいるディレクトリまで移動
cd %~dp0

echo [sub.bat] current directory: %CD%
endlocal

実行結果

E:\work\example>main.bat
[main.bat] current directory: E:\_work\example
[sub.bat] current directory: E:\_work\example
[sub.bat] current directory: E:\_work\example\subdir
[main.bat] current directory: E:\_work\example

E:\work\example>

【2: CDを記憶し帰り際にCDしなおす】

sub.bat

@echo off
echo [sub.bat] current directory: %CD%
set PARENT_CD=%CD%

rem sub.batがいるディレクトリまで移動
cd %~dp0

echo [sub.bat] current directory: %CD%

cd %PARENT_CD%

実行結果

E:\work\example>main.bat
[main.bat] current directory: E:\_work\example
[sub.bat] current directory: E:\_work\example
[sub.bat] current directory: E:\_work\example\subdir
[main.bat] current directory: E:\_work\example

E:\work\example>

Linux bashシェルスクリプトの知見

  • /bin/bashで別プロセスで コールされる(別プロセスが立ち上がる)ときは親プロセスのスコープを引き継ぎ、プロセスを戻るときに環境変数破棄
  • .source で呼び出されたら親プロセス内で処理されたことになり、共有スコープとなる(戻っても変更内容引継ぎ)

実験

ディレクトリ構成

|`- main.sh
 `- subdir
     `-sub.sh

main.sh

#!/bin/bash
echo [main]pwd: `pwd`
echo

cd ~/test
/bin/bash ./subdir/sub.sh
echo [main]pwd: `pwd`
echo

cd ~/test
. subdir/sub.sh
echo [main]pwd: `pwd`
echo

cd ~/test
source subdir/sub.sh
echo [main]pwd: `pwd`

sub.sh

#!/bin/bash
echo [sub]pwd: `pwd`
cd ~/test/subdir
echo [sub]pwd: `pwd`

実行結果

yu@yu:~/test$ ./main.sh
[main]pwd: /home/yu/test

[sub]pwd: /home/yu/test
[sub]pwd: /home/yu/test/subdir
[main]pwd: /home/yu/test

[sub]pwd: /home/yu/test
[sub]pwd: /home/yu/test/subdir
[main]pwd: /home/yu/test/subdir

[sub]pwd: /home/yu/test
[sub]pwd: /home/yu/test/subdir
[main]pwd: /home/yu/test/subdir

/bin/bashで蹴られたときだけcdしたものが自動で破棄。

また、$0 で見えるキックされたスクリプト名も、プロセスを共有していると親のものが参照される。