Giải Pháp Bash: NUF và Thay Thế Quy Trình
Giới thiệu
Trong vai trò kỹ sư DevOps, tôi thường dành nhiều thời gian để tối ưu hóa các quy trình CI/CD. GitHub Actions là một công cụ mạnh mẽ, nhưng đôi khi bạn cần phải thêm một chút logic tự động hóa tùy chỉnh để hoàn thiện trường hợp sử dụng của bạn. Gần đây, tôi đã gặp phải một vấn đề tưởng chừng đơn giản nhưng lại dẫn tôi vào thế giới Bash một cách sâu sắc.
Trong bài viết này, tôi sẽ chia sẻ cách tôi giải quyết vấn đề này, những mẹo tôi học được trong quá trình và lý do tại sao những mẹo này hiện đã trở thành những công cụ cần thiết trong bộ công cụ tự động hóa của tôi.
Tình huống Vấn đề
Tôi cần xây dựng một Workflow trên GitHub mà:
- Lặp qua tất cả các tệp đã thay đổi trong một Pull Request.
- Chạy một kịch bản cụ thể cho dịch vụ dựa trên các thư mục đã được sửa đổi.
- Đảm bảo rằng các kịch bản chỉ được chạy một lần cho mỗi dịch vụ duy nhất.
Nghe có vẻ dễ dàng, đúng không? Nhưng đây là những vấn đề tôi gặp phải:
- Tên tệp và thư mục chứa khoảng trắng, dấu gạch ngang và ký tự đặc biệt, gây ra những cơn ác mộng phân tách từ.
- Nhiều thư mục có sự thay đổi tệp chồng chéo, và tôi cần làm sạch chúng trước khi thực thi.
Những nỗ lực đơn giản với for file in $(git diff …) đã gặp phải sự cố ngay lập tức khi có khoảng trắng hoặc ký tự lạ xuất hiện. Việc loại bỏ trùng lặp với sort | uniq chỉ hoạt động một nửa, nhưng không đáng tin cậy khi tích hợp vào workflow.
Tôi cũng đã nghĩ đến việc giới thiệu bộ lọc đường dẫn trong trigger workflow để nhắm đến các thư mục cụ thể.
on:
pull_request:
paths:
- "services/service-a/**"
Nhưng điều này sẽ yêu cầu cập nhật workflow mỗi khi thêm thư mục mới, vì vậy không có vẻ như là một giải pháp mở rộng.
Tôi cần một giải pháp dựa trên bash an toàn, idempotent và thân thiện với CI.
Các bước Khắc phục và Giải quyết
Bước 1: Lấy các tệp đã thay đổi
Trong workflow, tôi đã lấy các tệp đã thay đổi bằng:
git diff --name-only origin/main...HEAD > changed_files.txt
Điều này đã cho tôi một danh sách phân tách bằng dòng mới. Tuyệt vời — cho đến khi tên tệp có khoảng trắng xuất hiện.
Bước 2: Xử lý khoảng trắng và ký tự đặc biệt
Tôi nhận ra rằng không thể chỉ dựa vào dòng mới. Tôi đã chuyển sang chuỗi phân tách NUL, dẫn đến mẹo quan trọng đầu tiên.
Bước 3: Loại bỏ trùng lặp thư mục
Tiếp theo, tôi cần cắt giảm mỗi đường dẫn xuống thư mục cấp cao nhất, sau đó loại bỏ trùng lặp. Một chút phép thuật với awk và sort -u đã làm nên điều kỳ diệu.
Bước 4: Lặp lại an toàn trong CI
Cuối cùng, tôi đã viết một vòng lặp để lặp qua mỗi thư mục duy nhất và chạy kịch bản liên quan. Chìa khóa là giữ cho nó bền vững chống lại mọi trường hợp ngoại lệ.
Mẹo Bash Nâng Cao #1: Xử lý Phân tách NUL
Trước khi đi vào sửa chữa, hãy nói về điều này.
Thông thường, khi bạn chạy một lệnh như git diff --name-only, nó in ra tên tệp phân tách bằng dòng mới (\n). Điều này thường hoạt động — cho đến khi bạn gặp phải tên tệp có khoảng trắng, dấu ngoặc kép, tab, hoặc thậm chí là biểu tượng cảm xúc. Bash thấy những khoảng trắng đó và nghĩ rằng chúng là “các phân tách” giữa các mục khác nhau, điều này có thể làm đổ vỡ các vòng lặp theo cách tinh vi.
Đó là lý do mà xử lý phân tách NUL trở nên hữu ích.
- Một ký tự NUL (
\0) là một ký tự đặc biệt vô hình đại diện cho “không gì cả.” Nó đảm bảo không bao giờ xuất hiện trong một tên tệp hợp lệ trên các hệ thống giống Unix. - Nếu chúng ta sử dụng NUL thay vì dòng mới làm phân tách, Bash có thể xử lý dữ liệu có cấu trúc một cách an toàn, bất kể các mục trông kỳ lạ đến đâu.
- Các lệnh như
git diff,xargsvàsortđều có các cờ (-zhoặc-0) để kích hoạt chế độ này.
Hãy nghĩ về nó như việc thay thế một phân tách dễ vỡ (dòng mới) bằng một phân tách không thể phá vỡ (NUL).
Đây là phần cốt lõi của việc sửa chữa:
git diff --name-only -z origin/main...HEAD | \
xargs -0 -n1 dirname | \
awk -F/ '{print $1}' | sort -u -z > changed_dirs.txt
Tại sao điều này hoạt động
-zkhiếngit diffxuất ra chuỗi phân tách NUL (\0) thay vì chuỗi phân tách bằng dòng mới.xargs -0đảm bảo rằng ngay cả các mục có khoảng trắng, dấu ngoặc kép hoặc biểu tượng cảm xúc (!) cũng được xử lý một cách an toàn.- Đến khi chúng ta đạt đến
sort -u -z, chúng ta đã có một danh sách sạch sẽ, đã loại bỏ trùng lặp của các thư mục cấp cao đã thay đổi.
Các trường hợp sử dụng trong thế giới thực
Xử lý phân tách NUL hữu ích không chỉ trong quy trình CI:
- Lặp qua dữ liệu có cấu trúc với khoảng trắng/ký tự đặc biệt: ví dụ, phân tích các khóa JSON được chuyển vào Bash.
jq -r '.keys[]' file.json | tr '\n' '\0' | xargs -0 -n1 echo
- Xử lý đầu vào người dùng hoặc tên tệp từ các nguồn bên ngoài: đổi tên hàng loạt ảnh có khoảng trắng trong tên.
- Xử lý an toàn nhật ký hoặc cấu hình: khi các mục có thể chứa khoảng trắng hoặc phân tách lạ.
Mẹo Bash Nâng Cao #2: Thay Thế Quy Trình
Hãy tạm dừng một chút — vì điều này có vẻ kỳ lạ lần đầu tiên bạn thấy nó.
Trong Bash, khi bạn muốn cung cấp đầu ra của một lệnh vào một lệnh khác, bạn thường sử dụng một ống (|) hoặc chuyển hướng đến một tệp tạm thời. Nhưng đôi khi, bạn cần đầu ra “giả vờ” là một tệp mà bạn có thể đọc từ đó.
Đó là nơi thay thế quy trình xuất hiện.
- Nó sử dụng cú pháp
< <(command)để bảo Bash: “Chạy lệnh này, và đối xử với đầu ra của nó như thể nó là một tệp mà tôi có thể đọc từ.” - Điều này tránh việc ghi các tệp tạm thời vào đĩa.
- Nó làm cho các vòng lặp và so sánh trở nên sạch hơn.
Hãy nghĩ về nó như việc tạo ra một tệp giả ngay lập tức, được hỗ trợ bởi một quy trình đang chạy.
Dưới đây là cách tôi đã sử dụng nó trong vòng lặp workflow:
while IFS= read -r -d '' dir; do
echo "Đang chạy kiểm tra cho $dir"
./scripts/run-checks.sh "$dir"
done < <(git diff --name-only -z origin/main...HEAD \
| xargs -0 -n1 dirname \
| awk -F/ '{print $1}' \
| sort -u -z)
Tại sao điều này hoạt động
< <(...)là thay thế quy trình, điều này cung cấp đầu ra của một lệnh trực tiếp vào một vòng lặp như thể nó là một tệp.- Không cần tệp tạm thời (
changed_dirs.txt). - Hoạt động liền mạch bên trong các runner của GitHub Actions.
Các trường hợp sử dụng trong thế giới thực
Thay thế quy trình tỏa sáng trong nhiều ngữ cảnh:
- Luồng dữ liệu có cấu trúc trực tiếp vào các vòng lặp: ví dụ, lặp qua một danh sách tài nguyên Kubernetes đã được lọc.
while read pod; do kubectl logs "$pod"; done < <(kubectl get pods -o name)
- So sánh ngay lập tức:
diff <(ls dir1) <(ls dir2)
- Pipeline xử lý dữ liệu: cung cấp nhật ký đã được chuyển đổi vào các kịch bản phân tích mà không tạo ra các tệp tạm thời.
- Quy trình song song: chuyển hướng đầu ra của các quy trình khác nhau vào cùng một vòng lặp để xử lý tổng hợp.
Đoạn mã Workflow Cuối cùng
Dưới đây là phiên bản đã được làm sạch hiện đang vận hành workflow của tôi:
jobs:
detect-and-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Chạy các kiểm tra cụ thể cho dịch vụ
run: |
while IFS= read -r -d '' dir; do
echo "Đang chạy kiểm tra cho $dir"
./scripts/run-checks.sh "$dir"
done < <(git diff --name-only -z origin/main...HEAD \
| xargs -0 -n1 dirname \
| awk -F/ '{print $1}' \
| sort -u -z)
Những điều rút ra quan trọng
- Đừng tin tưởng vào khoảng trắng trong CI — dữ liệu có cấu trúc có thể (và sẽ) làm gãy các vòng lặp đơn giản. Luôn xử lý nó một cách an toàn.
- Xử lý phân tách NUL (
-z,xargs -0,read -d '') là bạn tốt nhất cho sự bền vững. - Thay thế quy trình làm cho các kịch bản trở nên sạch hơn, tránh tệp tạm thời và mở ra cơ hội cho các so sánh mạnh mẽ ngay trong mã.
- Trong khi các bộ lọc đường dẫn của GitHub trông hấp dẫn, chúng không khả thi trong các kho phức tạp với nhiều dịch vụ. Một phương pháp dựa trên Bash cung cấp tính linh hoạt và độ bền cần thiết cho CI/CD trong thế giới thực.
Kể từ khi áp dụng những mẫu này, tôi đã tái sử dụng chúng trong nhiều workflow — từ linting đến triển khai chọn lọc — và chúng luôn hoạt động như mong đợi.
Bạn đã bao giờ gặp phải vấn đề khoảng trắng hoặc quy trình nhiều dịch vụ trong GitHub Actions chưa? Tôi rất muốn nghe cách bạn đã giải quyết chúng trong phần bình luận! Cảm ơn bạn đã dành thời gian.