1. Tổng quan.
Golang là một ngôn ngữ lập trình và os/exec
là một package trong Golang được sử dụng để thực hiện các lệnh hệ thống bên ngoài. Bạn có thể gặp khó khăn khi sử dụng package này, đặc biệt là khi bạn là người mới bắt đầu.
Dưới đây là một số câu hỏi phổ biến và giải đáp chúng:
- Làm thế nào để sử dụng os/exec với nhiều tham số?
- Bạn có thể sử dụng hàm
command
để tạo một đối tượng Cmd, sau đó sử dụng phương thứcArgs
để thiết lập danh sách các tham số.
- Bạn có thể sử dụng hàm
- Tại sao thư mục làm việc cwd của exec không phải là thư mục mà tôi đã gán?
- Điều này có thể xảy ra nếu bạn không thiết lập đúng thư mục làm việc. Bạn cần sử dụng phương thức
Dir
trên đối tượng Cmd để đặt thư mục làm việc mong muốn.
- Điều này có thể xảy ra nếu bạn không thiết lập đúng thư mục làm việc. Bạn cần sử dụng phương thức
- Làm thế nào để chuyển đổi lệnh chuyển tiếp vào cuộc gọi hàm exec?
- Bạn có thể sử dụng phương thức
StdinPipe
để tạo một đối tượng io.WriteCloser để viết dữ liệu vào tiến trình thực thi.
- Bạn có thể sử dụng phương thức
2. Một số ví dụ về cách sử dụng os/exec.
Dưới đây là một số ví dụ nhanh để minh họa cách sử dụng os/exec trong những tình huống phổ biến khi làm việc với các lệnh hệ thống hoặc các chương trình ngoại vi trong dự án của bạn.
Ví dụ 1 – Gọi hàm exec cơ bản.
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
// Tạo một đối tượng Cmd để thực hiện lệnh "ls -lah"
cmd := exec.Command("ls", "-lah")
// Thực hiện lệnh và lấy kết quả đầu ra kết hợp (stdout và stderr)
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
// In kết quả ra màn hình
fmt.Printf("combined out:\n%s\n", string(out))
}
Giải thích:
exec.Command("ls", "-lah")
: Tạo một đối tượng Cmd để thực hiện lệnh “ls -lah”.cmd.CombinedOutput()
: Thực hiện lệnh và trả về kết quả đầu ra kết hợp (stdout và stderr).- Nếu có lỗi (
err
không nil) in thông báo lỗi và kết thúc chương trình. - In kết quả ra màn hình.
Ví dụ 2 – Gọi hàm exec với biến môi trường bổ sung.
package main
import (
"fmt"
"log"
"os"
"os/exec"
)
func main() {
// Tạo một đối tượng Cmd để thực hiện một chương trình tùy ý
cmd := exec.Command("programToExecute")
// Tạo một biến môi trường bổ sung ("FOO=bar") và thêm vào danh sách môi trường hiện tại
additionalEnv := "FOO=bar"
newEnv := append(os.Environ(), additionalEnv)
cmd.Env = newEnv
// Thực hiện lệnh và lấy kết quả đầu ra kết hợp (stdout và stderr)
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
// In kết quả ra màn hình
fmt.Printf("%s", out)
}
Giải thích:
exec.Command("programToExecute")
: Tạo một đối tượng Cmd để thực hiện một chương trình tùy ý.additionalEnv := "FOO=bar"
: Tạo một biến môi trường bổ sung “FOO=bar”.newEnv := append(os.Environ(), additionalEnv)
: Thêm biến môi trường bổ sung vào danh sách môi trường hiện tại.cmd.Env = newEnv
: Đặt danh sách môi trường mới cho đối tượng Cmd.- Thực hiện lệnh và xử lý kết quả tương tự như trong ví dụ 1.
Cả hai ví dụ đều thực hiện lệnh và lấy kết quả đầu ra nhưng ví dụ thứ hai minh họa cách bạn có thể thêm biến môi trường bổ sung khi gọi lệnh.
Ví dụ 3 – Thiết lập thư mục làm việc (CWD) cho đường dẫn thực thi.
package main
import (
"fmt"
"log"
"os/exec"
"path/filepath"
)
func main() {
// Cách 1: Thiết lập thư mục làm việc trực tiếp
cmd := exec.Command("git", "log")
cmd.Dir = "your/intended/working/directory"
out, err := cmd.Output()
if err != nil {
log.Fatalf("cmd.Output() failed with %s\n", err)
}
fmt.Printf("%s", out)
// Cách 2: Sử dụng đường dẫn tuyệt đối của binary
abspath, _ := filepath.Abs("./mybinary")
cmd = exec.Command(abspath, "log")
cmd.Dir = "your/intended/working/directory"
out, err = cmd.Output()
if err != nil {
log.Fatalf("cmd.Output() failed with %s\n", err)
}
fmt.Printf("%s", out)
}
Giải thích:
cmd.Dir = "your/intended/working/directory"
: Thiết lập thư mục làm việc cho đối tượng Cmd.
Ví dụ 4 – Sử dụng os/exec với lệnh bash trên Linux.
package main
import (
"log"
"os/exec"
)
func main() {
rcmd := `iw dev | awk '$1=="Interface"{print $2}'`
// Sử dụng "bash -c" để chạy lệnh bash
cmd := exec.Command("bash", "-c", rcmd)
// Lấy kết quả đầu ra kết hợp (stdout và stderr)
out, err := cmd.CombinedOutput()
if err != nil {
log.Println(err.Error())
}
log.Println(string(out))
}
Giải thích:
cmd := exec.Command("bash", "-c", rcmd)
: Tạo một đối tượng Cmd để chạy lệnh bash với nội dung được xác định bởi biếnrcmd
.cmd.CombinedOutput()
: Lấy kết quả đầu ra kết hợp (stdout và stderr) của lệnh bash.- Nếu có lỗi, in ra thông báo lỗi.
Ví dụ 5 – Sử dụng os/exec với lệnh Batch trên Windows.
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
// Sử dụng "cmd /c" để chạy lệnh Batch trên Windows
cmd := exec.Command("cmd", "/c", "ffmpeg -i myfile.mp4 myfile.mp3 && del myfile.mp4")
// Lấy kết quả đầu ra kết hợp (stdout và stderr)
out, err := cmd.CombinedOutput()
if err != nil {
log.Println(err.Error())
}
log.Println(string(out))
}
Giải thích:
cmd := exec.Command("cmd", "/c", "ffmpeg -i myfile.mp4 myfile.mp3 && del myfile.mp4")
: Tạo một đối tượng Cmd để chạy lệnh Batch với nội dung được xác định bởi chuỗi “ffmpeg -i myfile.mp4 myfile.mp3 && del myfile.mp4”.cmd.CombinedOutput()
: Lấy kết quả đầu ra kết hợp (stdout và stderr) của lệnh Batch.- Nếu có lỗi, in ra thông báo lỗi.
Ví dụ 6 – Sử dụng string slice khi gọi hàm os/exec.
package main
import (
"fmt"
"os/exec"
)
func main() {
// Sử dụng slice chuỗi để truyền tham số
args := []string{"hello", "world"}
cmd := exec.Command("echo", args...)
// Lấy kết quả đầu ra
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(out))
}
Giải thích:
args := []string{"hello", "world"}
: Tạo một slice chuỗi với các tham số bạn muốn truyền.cmd := exec.Command("echo", args...)
: Tạo một đối tượng Cmd với lệnh “echo” và tham số từ slice chuỗi.cmd.Output()
: Lấy kết quả đầu ra của lệnh “echo” với các tham số đã được chỉ định.- Nếu có lỗi, in ra thông báo lỗi.
Ví dụ 7 – Giải quyết vấn đề về localization trong Windows CMD cho os/exec.
package main
import (
"log"
"os/exec"
"strings"
)
func main() {
// Sử dụng "cmd /c" để chạy lệnh Batch trên Windows
cmd := exec.Command("cmd", "/c", "chcp 65001 && netsh WLAN show drivers")
// Lấy kết quả đầu ra kết hợp (stdout và stderr)
out, err := cmd.CombinedOutput()
if err != nil {
log.Println(err.Error())
}
// Chuyển đổi kết quả thành chuỗi và tách dòng
outputLines := strings.Split(string(out), "\n")
// Lọc và in các dòng từ dòng thứ 2 trở đi (loại bỏ dòng xác nhận chcp)
for i, line := range outputLines {
if i > 0 {
log.Println(line)
}
}
}
Giải thích:
cmd := exec.Command("cmd", "/c", "chcp 65001 && netsh WLAN show drivers")
: Tạo một đối tượng Cmd để chạy lệnh Batch với nội dung được xác định bởi chuỗi “chcp 65001 && netsh WLAN show drivers”.chcp 65001
là lệnh để chuyển đổi mã codepage sang UTF-8.cmd.CombinedOutput()
: Lấy kết quả đầu ra kết hợp (stdout và stderr) của lệnh Batch.strings.Split(string(out), "\n")
: Chia kết quả thành các dòng riêng biệt.for i, line := range outputLines
: Duyệt qua từng dòng của kết quả.if i > 0
: Kiểm tra để đảm bảo rằng chỉ in các dòng từ dòng thứ hai trở đi (loại bỏ dòng xác nhận chcp).log.Println(line)
: In các dòng sau khi đã xác định được mã codepage.
Ví dụ 7 – Ví dụ tổng hợp.
Ở ví dụ này mình sử dụng các module như sau:
encoding/json
: Gói này cung cấp các chức năng để mã hóa và giải mã dữ liệu JSON trong Go. Trong mã của bạn, gói này được sử dụng để chuyển đổi slice của FileSystemInfo
thành định dạng JSON thông qua hàm json.MarshalIndent
. Nó giúp bạn có thể dễ dàng chuyển đổi giữa dữ liệu Go và dữ liệu JSON.
import "encoding/json"
fmt
: Gói fmt
cung cấp các chức năng để định dạng và in dữ liệu. Trong mã của bạn, fmt
được sử dụng để in định dạng JSON vào màn hình.
import "fmt"
os/exec
: Gói os/exec
cung cấp các chức năng để thực hiện các lệnh hệ thống và tương tác với chúng. Trong mã của bạn, os/exec
được sử dụng để chạy lệnh hệ thống df -h
và thu thập đầu ra của nó.
import "os/exec"
strings
: Gói strings
cung cấp các chức năng để xử lý chuỗi. Trong mã của bạn, strings
được sử dụng để tách và xử lý dữ liệu đầu ra từ lệnh df -h
.
import "strings"
Tổng cộng, các gói này giúp bạn thực hiện các nhiệm vụ như thực thi lệnh hệ thống, xử lý và tách chuỗi, định dạng và in dữ liệu, cũng như chuyển đổi giữa dữ liệu Go và định dạng JSON.
Dưới đây là đoạn code sử dụng các module đã liệt kê ở trên, mình chạy lệnh df -h
trong Linux bằng Golang, có thể sử dụng gói os/exec
để thực hiện lệnh và thu thập kết quả, sau đó tách các giá trị từ đầu ra của lệnh df -h
và chuyển chúng thành định dạng JSON, bạn có thể sử dụng các gói như strings
và encoding/json
.
package main
import (
"encoding/json"
"fmt"
"os/exec"
"strings"
)
type FileSystemInfo struct {
Filesystem string `json:"filesystem"`
Size string `json:"size"`
Used string `json:"used"`
Avail string `json:"avail"`
UsePercent string `json:"use_percent"`
MountedOn string `json:"mounted_on"`
}
func main() {
cmd := exec.Command("df", "-h")
output, err := cmd.Output()
if err != nil {
fmt.Println("Lỗi khi chạy lệnh:", err)
return
}
// Tách các dòng từ kết quả
lines := strings.Split(string(output), "\n")
// Kiểm tra nếu slice lines không chứa ít nhất 2 phần tử (tiêu đề và một dòng dữ liệu)
if len(lines) < 2 {
fmt.Println("Không có đầu ra hợp lệ từ lệnh df -h.")
return
}
var fileSystems []FileSystemInfo
// Bắt đầu từ dòng thứ hai để bỏ qua dòng tiêu đề
for i := 1; i < len(lines); i++ {
// Tách các trường từ dòng
fields := strings.Fields(lines[i])
// Kiểm tra xem dòng có đủ trường không
if len(fields) >= 6 {
fs := FileSystemInfo{
Filesystem: fields[0],
Size: fields[1],
Used: fields[2],
Avail: fields[3],
UsePercent: fields[4],
MountedOn: fields[5],
}
fileSystems = append(fileSystems, fs)
} else {
fmt.Println("Dòng không đủ trường:", lines[i])
}
}
// Chuyển slice thành JSON
jsonData, err := json.MarshalIndent(fileSystems, "", " ")
if err != nil {
fmt.Println("Lỗi khi chuyển đổi thành JSON:", err)
return
}
// In kết quả JSON
fmt.Println(string(jsonData))
}
Câu lệnh var fileSystems []FileSystemInfo
trong ngôn ngữ Go (Golang) đang khai báo một biến có tên là fileSystems
là một slice (mảng động) chứa các phần tử thuộc kiểu dữ liệu FileSystemInfo
. Trong ngữ cảnh này, FileSystemInfo
là một kiểu dữ liệu được xác định ở nơi khác trong mã nguồn của bạn.
Nếu FileSystemInfo
là một kiểu dữ liệu (struct) nào đó đã được định nghĩa trước đó trong mã nguồn của bạn, thì biến fileSystems
sẽ là một slice chứa các phần tử thuộc kiểu dữ liệu đó.
var fileSystems []FileSystemInfo
Mình sử dụng gói strings
để tách dòng và trường từ kết quả lệnh. Sau đó, mình sử dụng một struct FileSystemInfo
để lưu trữ thông tin từng dòng và sử dụng gói encoding/json
để chuyển đổi slice của các FileSystemInfo
thành định dạng JSON.
Tiếp theo mình đã thêm các điều kiện kiểm tra trước khi truy cập các phần tử trong slice để tránh lỗi “index out of range”. Nếu một dòng không đủ trường, mình sẽ cho in thông báo lỗi và tiếp tục với dòng tiếp theo.
Kết quả của lệnh df -f của tôi khi gõ trong terminal là.
$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 1.9G 0 1.9G 0% /dev
tmpfs 392M 1.1M 391M 1% /run
/dev/sda1 97G 4.1G 93G 5% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/loop2 41M 41M 0 100% /snap/snapd/20290
/dev/loop1 92M 92M 0 100% /snap/lxd/24061
/dev/loop0 64M 64M 0 100% /snap/core20/2015
/dev/sda15 105M 6.1M 99M 6% /boot/efi
tmpfs 392M 0 392M 0% /run/user/0
Và sau khi dùng Golang để phân tích thành JSON.
$ go run main.go
Dòng không đủ trường:
[
{
"filesystem": "udev",
"size": "1.9G",
"used": "0",
"avail": "1.9G",
"use_percent": "0%",
"mounted_on": "/dev"
},
{
"filesystem": "tmpfs",
"size": "392M",
"used": "1.1M",
"avail": "391M",
"use_percent": "1%",
"mounted_on": "/run"
},
{
"filesystem": "/dev/sda1",
"size": "97G",
"used": "4.1G",
"avail": "93G",
"use_percent": "5%",
"mounted_on": "/"
},
{
"filesystem": "tmpfs",
"size": "2.0G",
"used": "0",
"avail": "2.0G",
"use_percent": "0%",
"mounted_on": "/dev/shm"
},
{
"filesystem": "tmpfs",
"size": "5.0M",
"used": "0",
"avail": "5.0M",
"use_percent": "0%",
"mounted_on": "/run/lock"
},
{
"filesystem": "tmpfs",
"size": "2.0G",
"used": "0",
"avail": "2.0G",
"use_percent": "0%",
"mounted_on": "/sys/fs/cgroup"
},
{
"filesystem": "/dev/loop2",
"size": "41M",
"used": "41M",
"avail": "0",
"use_percent": "100%",
"mounted_on": "/snap/snapd/20290"
},
{
"filesystem": "/dev/loop1",
"size": "92M",
"used": "92M",
"avail": "0",
"use_percent": "100%",
"mounted_on": "/snap/lxd/24061"
},
{
"filesystem": "/dev/loop0",
"size": "64M",
"used": "64M",
"avail": "0",
"use_percent": "100%",
"mounted_on": "/snap/core20/2015"
},
{
"filesystem": "/dev/sda15",
"size": "105M",
"used": "6.1M",
"avail": "99M",
"use_percent": "6%",
"mounted_on": "/boot/efi"
},
{
"filesystem": "tmpfs",
"size": "392M",
"used": "0",
"avail": "392M",
"use_percent": "0%",
"mounted_on": "/run/user/0"
}
]
Tham khảo nguồn https://dev.to/tobychui/quick-notes-for-go-os-exec-3ejg
.