一個極簡 Go HTTP 服務,接收路徑參數,呼叫系統 `lsof` 命令查詢該檔案被哪些進程佔用,回傳佔用進程數量。展示 Go 執行外部命令、管道串接(`lsof | wc -l`)、HTTP 處理的核心模式。
package main
import (
"fmt"
"net/http"
"os/exec"
"flag"
)
func main() {
http.HandleFunc("/", readHandle)
port := flag.Int("port", 2012, "Listen Port")
flag.Parse()
addr := fmt.Sprintf(":%d", *port)
fmt.Printf("Server listening on %s\n", addr)
http.ListenAndServe(addr, nil)
}
func readHandle(w http.ResponseWriter, r *http.Request) {
// 1. 獲取查詢參數 path
path := r.FormValue("path")
if path == "" {
http.Error(w, "Missing 'path' parameter", http.StatusBadRequest)
return
}
// 2. 建立命令管道:lsof path | wc -l
lsofCmd := exec.Command("lsof", path)
wcCmd := exec.Command("wc", "-l")
// 3. 串接管道:lsof stdout → wc stdin
lsofOut, err := lsofCmd.StdoutPipe()
if err != nil {
http.Error(w, "Pipe setup failed", http.StatusInternalServerError)
return
}
wcCmd.Stdin = lsofOut
// 4. 啟動 lsof
if err := lsofCmd.Start(); err != nil {
http.Error(w, "lsof start failed", http.StatusInternalServerError)
return
}
// 5. 執行 wc 並獲取輸出(會阻塞直到 lsof 結束)
out, err := wcCmd.Output()
if err != nil {
http.Error(w, "wc execution failed", http.StatusInternalServerError)
return
}
// 6. 等待 lsof 結束(回收資源)
lsofCmd.Wait()
// 7. 回傳結果
fmt.Fprintf(w, "Path: %s\n", path)
fmt.Fprintf(w, "Process count: %s", string(out))
fmt.Fprintf(w, "Request URL path: %s\n", r.URL.Path[1:])
}
| 技術 | 說明 |
|---|---|
exec.Command | 建立外部命令對象,支援參數切片,自動處理路徑搜尋 |
StdoutPipe() | 返回連接命令標準輸出的管道(`io.Reader`),啟動命令後可讀取 |
| 管道串接 | 將 `lsofCmd.StdoutPipe()` 賦給 `wcCmd.Stdin`,實現 `lsof | wc` |
Start() vs Run() | `Start()` 非阻塞啟動,需手動 `Wait()`;`Run()` = `Start()` + `Wait()` |
Output() | 運行命令並捕獲標準輸出(內部自動 `Start`+`Wait`),此處用於 `wc` |
flag.Int | 命令列參數解析,預設埠 2012 |
# 編譯
go build -o lsof_server main.go
# 執行(預設埠 2012)
./lsof_server
# 或指定埠
./lsof_server -port=8080
# 查詢 /var/log/syslog 被誰佔用
curl "https://localhost:2012/?path=/var/log/syslog"
# 輸出範例
Path: /var/log/syslog
Process count: 2
Request URL path:
避免外部命令依賴,可直接讀取 `/proc/
func getProcessesHoldingFile(target string) ([]int, error) {
var pids []int
entries, _ := os.ReadDir("/proc")
for _, e := range entries {
if pid, err := strconv.Atoi(e.Name()); err == nil {
fdDir := fmt.Sprintf("/proc/%d/fd", pid)
links, _ := os.ReadDir(fdDir)
for _, l := range links {
dest, _ := os.Readlink(fdDir + "/" + l.Name())
if dest == target {
pids = append(pids, pid)
break
}
}
}
}
return pids, nil
}
優點:無外部依賴、跨平台(Linux)、效率更高。缺點:需讀取 `/proc` 權限、Linux 專用。