﻿# 直接將 T-SQL 命令透過 STDIN 的方式傳入 SQL Server Linux 容器執行的方法

我先前寫過兩篇文章，一篇是 [使用 Docker 執行 SQL Server on Linux 容器之常用工具與命令](/post/2020/08/04/Docker-SQL-Server-on-Linux) 分享各種常見的執行命令，另一篇則是 [深入剖析 docker run 與 docker exec 的 -i 與 -t 技術細節](/post/2022/11/22/docker-run-docker-exec-docker-attach-tty-and-stdin) 幫助我更加理解 Docker 處理 STDIN 與 Virtual Terminal (VT) 之間的關係。這讓我連結到一個長久以來希望被微軟實現的需求，如果我想直接透過 STDIN 將 T-SQL 傳入 SQL Server on Linux 容器執行，不想先把本機的 T-SQL 檔案複製進去，那該如何處理？想不到還真的給我研究出方法了！

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/a76c06c24f9534443f41-208598577-4b5ef4a3-de35-447e-9cdd-844cf7e0f0d8.png)

> 我當時在 [使用 Docker 執行 SQL Server on Linux 容器之常用工具與命令](/post/2020/08/04/Docker-SQL-Server-on-Linux) 文章中，所寫的範例都以 SQL Server 2019 為主，這篇文章我打算改用 SQL Server 2022 來做示範，但基本上是一樣的。

以下就是完整的實作過程：

1.  建立 SQL Server 2022 on Linux 容器
    
    ```bash
    docker run --rm -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Ver7CompleXPW" -p 1433:1433 --name sql1 -d mcr.microsoft.com/mssql/server:2022-latest
    ```
    
2.  查看 SQL Server 容器是否順利啟動
    
    ```bash
    docker logs sql1
    ```
    
3.  查看 SQL Server 容器版本資訊
    
    ```bash
    docker exec -it sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -Q "SELECT @@VERSION"
    ```
    
4.  下載 `ContosoUniversity` 範例資料庫的 T-SQL 檔案
    
    ```bash
    curl -sSLO https://gist.github.com/doggy8088/2a2f7075d49b3814d19513426ede3549/raw/ba8c4e7d46597188a17a39de7906d358f18d834d/ContosoUniversity.sql
    ```
    
5.  匯入 `ContosoUniversity` 範例資料庫到 SQL Server 容器中
    
    這裡就是本篇文章的精華所在了，因為我不想透過 `docker cp` 把 `ContosoUniversity.sql` 檔案複製到容器中，再用 `sqlcmd` 的 `-i` 參數傳入檔案路徑，所以想方設法看能不能透過 `STDIN` 傳入 T-SQL 執行，直到我找到 [sqlcmd does not accept queries from standard input #2](https://github.com/microsoft/homebrew-mssql-release/issues/2) 這篇討論，但微軟在留言中回覆支援 `STDIN` 不在計畫內，就把 Issue 給關閉了，殘酷的拒絕了這個想法！🔥
    
    其實透過 `STDIN` 傳入應用程式在 Linux 世界是非常常見的用法，還好有善心人在這則 Issue 的下方提供了 **Workarround** (應變措施)，想不到還真的有效，實在是太棒了！😍
    
    在 Linux 作業系統有個特殊的 `/dev/stdin` 檔案，嚴格說起來它並不是一個「檔案」，而是一個「虛擬裝置」(pseudo-devices)，只是你可以把他當成「檔案」來操作。這個 `/dev/stdin` 檔案的內容，恰巧就是**虛擬終端機**(VT)中「標準輸入」的內容，而這正好可以拿來當成 `sqlcmd` 工具的 `-i inputfile` 參數值！👍
    
    Windows PowerShell / Command Prompt
    
    ```bash
    type ContosoUniversity.sql | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
    ```
    
    Bash
    
    ```bash
    cat ContosoUniversity.sql | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
    ```
    

### 改寫原本文章的語法

如果要執行 `SELECT sys.databases` 命令，以前我會用 `-Q` 參數，直接在參數傳入執行，但現在我會改成這樣寫了：

```bash
echo "SELECT name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
```

或是將欄位調整短一點，讓命令列模式下輸出結果比較好看：

```bash
echo "SELECT CAST(name as varchar(30)) as name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
```

![echo "SELECT CAST(name as varchar(30)) as name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/eb17977d223a6113a7b4-208599160-425abe59-bb15-4f0d-b818-89b8cf1f8fc8.png)

如果要列出 `ContosoUniversity` 資料庫中有多少表格，我就會這樣寫：

```bash
echo 'SELECT CAST(name as varchar(30)) as name FROM sys.tables' | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -d 'ContosoUniversity' -i /dev/stdin
```

如果要列出 `ContosoUniversity` 資料庫中\`\`Course\` 表格的內容，我就會這樣寫：

```bash
echo 'SELECT * FROM dbo.Course' | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -d 'ContosoUniversity' -i /dev/stdin
```

基本上顯示中文也不會有問題：

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/ae3cbad2974d25f30d95-208404770-b8544616-ced1-4c30-9723-52e41bec3d10.png)

### 總結

學會了這個技巧，我在 CI 與 CD 的 Pipelines 就可以寫的更漂亮些，而且也不會佔用容器中的空間，自動化建置 Container Image 的過程也會更加方便，實在是太棒了！👍

### 相關連結

-   [Docker: Install containers for SQL Server on Linux - SQL Server | Microsoft Learn](https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-ver16&pivots=cs1-powershell&WT.mc_id=DT-MVP-4015686)
-   [sqlcmd does not accept queries from standard input · Issue #2 · microsoft/homebrew-mssql-release](https://github.com/microsoft/homebrew-mssql-release/issues/2)
-   [What are `/dev/stdout` and `/dev/stdin`? What are they useful for?](https://jameshfisher.com/2018/03/31/dev-stdout-stdin/)
