2026年1月6日 星期二

深入解析 grep 的「Broken pipe」錯誤:隨機出現的原因與優雅解法

最近我在 [ChromeOS Flex](https://chromeos.google/products/chromeos-flex/) 的 Linux 環境中使用 [Homebrew](https://brew.sh/) 時,總是會隨機看到 `grep: write error: Broken pipe`。後來去檢查相關程式碼,發現是這行 `grep -E "^(flags|Features)" /proc/cpuinfo | grep -q "ssse3"` 指令造成的。 這個錯誤雖然不一定影響最終結果,但在特定環境下出現總是讓人感到困惑。為什麼這行看似普通的管線指令會隨機報錯?今天就來深入探討其成因,並提供修正方案。 ## 「破裂的管線」錯誤成因 這個錯誤的核心,在於 Linux 作業系統處理**行程(Process)**與**管線(Pipe)**的機制。 當我們執行上述指令時,發生了以下連鎖反應: 1. **前端寫入(Producer)**: 第一個指令 `grep -E ...` 負責讀取 CPU 資訊,並持續將符合條件的資料寫入管線。 2. **後端讀取與提早退出(Consumer)**: 管線另一端的 `grep -q "ssse3"` 負責接收資料。關鍵在於 `-q` (quiet) 參數,它告訴 `grep` 一旦找到 "ssse3",就**立即停止讀取並退出程式**。 3. **管線斷裂(Broken Pipe)**: 當後端 `grep` 退出後,管線的讀取端隨之關閉。然而,前端的 `grep` 此時可能還在嘗試寫入剩餘的資料。當它試圖寫入這條「已關閉」的管線時,系統便會發送 **SIGPIPE** 訊號,導致行程終止並印出 `write error: Broken pipe`。 ## 為何錯誤是隨機出現的? 既然機制如此,為何不是「每次」都報錯?這歸因於**競爭條件(Race Condition)**。 電腦的排程器決定了兩個程式執行的快慢: * **沒事發生的情況**:前端 `grep` 產出資料的速度夠快,或者資料量剛好夠小,在後端退出前就已經寫完並結束了。 * **報錯的情況**:後端 `grep -q` 很快就找到了 "ssse3" 並關閉管線,而前端還來不及寫完剩下的資料,導致寫入失敗。 這種執行時間上的微小差異,決定了你是否會看到這行錯誤訊息。 ## 更優雅的解決方案 既然問題出在「多個指令透過管線傳輸」的時間差,最穩健的解法就是**避免使用管線**,將邏輯整合到單一 `grep` 指令中。 我們可以將原本的指令: ```bash grep -E "^(flags|Features)" /proc/cpuinfo | grep -q "ssse3" ``` 改寫為: ```bash grep -qE '^(flags|Features).*\bssse3\b' /proc/cpuinfo ``` ### 改寫後的優點: * **消除競爭條件**:單一行程運作,完全根除「管線破裂」的可能性。 * **效能提升**:省去了啟動兩個行程與建立管線的開銷。 * **精準度提高**: * `^(flags|Features)`:鎖定行首。 * `.*\bssse3\b`:利用 `\b`(單詞邊界)精確匹配 "ssse3",避免誤判類似字串。 下次若在 Script 中發現類似的隨機管線錯誤,不妨檢查是否使用了會提早退出的指令(如 `grep -q`、`head` 等),並嘗試將其整合以提升穩定性! 順便生了一個 [pull request](https://github.com/Homebrew/brew/pull/21366) 給 Homebrew

2026年1月5日 星期一

自幹了一個 Agent Skills - Launchpad Skill

放在 https://github.com/fourdollars/lp-api/ 專案底下的 launchpad 目錄底下,當然就是用 lp-api 這個小工具來串接。

我自己有在 OpenCode, GitHub Copilot CLI, Gemini CLI 上面成功掛載起來使用。

基本上就是把 AI Agent 接上 Launchpad,用自然語言去叫 AI 做自己想要做的事情。

以下是幾張 OpenCode 上使用的示範。

有興趣的人可以去看看 Agent Skills 了解一下。

2025年11月28日 星期五

Kadane's algorithm

昨天練習的題目是 3381. Maximum Subarray Sum With Length Divisible by K,題目的最佳解是利用 Kadane's algorithm,我覺得比較容易理解的方式是先計算前面 k-1 個 kSum 之後,再往後面用 Kadane's algorithm 來解。

如果是寫 C++ 是這樣:

class Solution {
public:
    long long maxSubarraySum(vector< int>& nums, int k) {
        const int n = nums.size();
        vector< long long> kSum(k, 0);
        long long maxSum = numeric_limits< long long>::min(), prefixSum = 0;
        for (int i = 0; i < k - 1; ++i) {
            prefixSum += nums[i];
            kSum[i] = prefixSum;
        }
        for (int i = k - 1; i < n; ++i) {
            prefixSum += nums[i];
            int r = i % k;
            maxSum = max(maxSum, prefixSum - kSum[r]);
            kSum[r] = min(kSum[r], prefixSum);
        }
        return maxSum;
    }
};

寫 Python3 是這樣:

class Solution:
    def maxSubarraySum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        kSum = [0] * k
        prefixSum = 0
        maxSum = float("-inf")
        for i in range(k - 1):
            prefixSum += nums[i]
            kSum[i] = prefixSum
        for i in range(k - 1, n):
            prefixSum += nums[i]
            r = i % k
            maxSum = max(maxSum, prefixSum - kSum[r])
            kSum[r] = min(kSum[r], prefixSum)
        return maxSum

寫 Go 是這樣,這邊要注意即便是使用 math.MinInt64 還是要用 int64() 明確轉型,不然預設會用 int 然後就會跟後面的 int64 衝突。

func maxSubarraySum(nums []int, k int) int64 {
	n := len(nums)
	kSum := make([]int64, k)
	prefixSum, maxSum := int64(0), int64(math.MinInt64)
	for i := range n {
		prefixSum += int64(nums[i])
		if i < k-1 {
			kSum[i] = prefixSum
			continue
		}
		r := i % k
		maxSum = max(maxSum, prefixSum-kSum[r])
		kSum[r] = min(kSum[r], prefixSum)
	}
	return maxSum
}

寫 Rust 是這樣,沒什麼特別要注意的地方,反正寫錯時編譯器會提醒你該如何修正。 :-P

impl Solution {
    pub fn max_subarray_sum(nums: Vec< i32>, k: i32) -> i64 {
        let k = k as usize;
        let n = nums.len();
        let mut kSum = vec![0i64; k];
        let mut prefixSum = 0i64;
        let mut maxSum = i64::MIN;
        for i in 0..(k-1) {
            prefixSum += nums[i] as i64;
            kSum[i] = prefixSum;
        }
        for i in (k-1)..n {
            prefixSum += nums[i] as i64;
            let r = i % k;
            maxSum = maxSum.max(prefixSum - kSum[r]);
            kSum[r] = kSum[r].min(prefixSum);
        }
        maxSum
    }
}

不曉得為何,寫 Rust 有最愉快的感覺。 :-P

2025年11月17日 星期一

Binary Search

今日練習的題目是 300. Longest Increasing Subsequence 主要是需要使用到 Binary Search

首先是 C++ 的 lower_bound

class Solution {
public:
    int lengthOfLIS(vector< int>& nums) {
        vector< int> arr;
        arr.reserve(nums.size());
        for (auto& num : nums) {
            auto ptr = ranges::lower_bound(arr.begin(), arr.end(), num);
            if (ptr == arr.end()) {
                arr.push_back(num);
            } else {
                *ptr = num;
            }
        }
        return arr.size();
    }
};

然後是 Python 的 bisect.bisect_left()

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        arr = []
        for num in nums:
            i = bisect.bisect_left(arr, num)
            if i == len(arr):
                arr.append(num)
            else:
                arr[i] = num
        return len(arr)

然後是 Go 的 sort.Search()

func lengthOfLIS(nums []int) int {
	arr := make([]int, 0, len(nums))
	for _, num := range nums {
		idx := sort.Search(len(arr), func(i int) bool {
			return arr[i] >= num
		})
		if idx == len(arr) {
			arr = append(arr, num)
		} else {
			arr[idx] = num
		}
	}
	return len(arr)
}

最後是 Rust 的 binary_search

impl Solution {
    pub fn length_of_lis(nums: Vec< i32>) -> i32 {
        let mut arr = Vec::with_capacity(nums.len());
        for num in nums {
            let i = match arr.binary_search(&num) {
                Ok(index) => index,
                Err(index) => index,
            };
            if i == arr.len() {
                arr.push(num);
            } else {
                arr[i] = num;
            }
        }
        arr.len() as i32
    }
}

每種程式語言使用方式都有所不同。

2025年11月8日 星期六

Gray code

今天練習的題目是 1611. Minimum One Bit Operations to Make Integers Zero,後來自己想出解法後去看官方解答才發現是在考 Gray code 的轉換。

我自己列出 16 轉換到 0 的所有 bits 的表現方式,然後仔細觀察後發現有某種規律,於是就猜想了一個方程式出來試試看,結果還真的可以解答,先聲明一下這不是最有效的方法,只是眾多解法中的其中一種而已。

設 $p_1, p_2, \dots, p_j$ 為 $n$ 的二進位中所有 1 的 0-based 索引,且 $p_1 > p_2 > \dots > p_j$ ($p_1$ 是 MSB)。
$$f(n) = \sum_{i=1}^{j} (-1)^{i-1} \cdot (2^{p_i+1} - 1)$$

這是我初始第一個版本

class Solution {
public:
    int minimumOneBitOperations(int n) {
        if (n == 0)
            return 0;
        int k = 32 - __builtin_clz(n);
        int ans = 0;
        bitset<30> arr(n);
        int sign = 1;
        for (int i = k; i > 0; --i) {
            if (arr[i - 1]) {
                ans += sign * ((1 << i) - 1);
                sign *= -1;
            }
        }
        return ans;
    }
};

簡化後的第二個版本

class Solution {
public:
    int minimumOneBitOperations(int n) {
        if (n == 0)
            return 0;
        int k = 32 - __builtin_clz(n);
        int ans = 0;
        int sign = 1;
        for (int i = k; i > 0; --i) {
            if (n & (1 << (i - 1))) {
                ans += sign * ((1 << i) - 1);
                sign *= -1;
            }
        }
        return ans;
    }
};

再進一步簡化後的第三個版本

class Solution {
public:
    int minimumOneBitOperations(int n) {
        if (n == 0)
            return 0;
        int ans = 0;
        int sign = 1;
        while (n) {
            int k = 32 - __builtin_clz(n);
            ans += sign * ((1 << k) - 1);
            sign *= -1;
            n ^= (1 << (k-1));
        }
        return ans;
    }
};

有用 Gemini AI 幫忙寫了一份解答分享在 LeetCode 上面 Using a Math Formula 這樣

2025年10月28日 星期二

accumulate/sum 加總

今天練習的題目是 3354. Make Array Elements Equal to Zero,題目可以利用加總來解題。

首先是 C++ 要使用 accumulate

class Solution {
public:
    int countValidSelections(vector< int>& nums) {
        int ans = 0, prev = 0, post = accumulate(nums.begin(), nums.end(), 0);
        for (auto num : nums) {
            if (num == 0) {
                if (prev == post)
                    ans += 2;
                else if (abs(prev - post) == 1)
                    ++ans;
            } else {
                post -= num;
                prev += num;
            }
        }
        return ans;
    }
};

Python3 就用 sum()

class Solution:
    def countValidSelections(self, nums: List[int]) -> int:
        ans, prev, post = 0, 0, sum(nums)
        for num in nums:
            if num == 0:
                if prev == post:
                    ans += 2
                elif abs(prev - post) == 1:
                    ans += 1
            else:
                prev += num
                post -= num
        return ans

Go 加總要自己來,abs() 也要自己刻,不過發現可以利用 slices.Values() 在 range 上面,這樣就不用多寫一個 _ 底線,像是 for _, num := range nums 這樣,不過多寫 slices.Values 也不一定好到哪裡去。

func abs(num int) int {
    if num < 0 {
        return -num
    }
    return num
}

func countValidSelections(nums []int) int {
    ans, prev, post := 0, 0, 0
    for num := range slices.Values(nums) {
        post += num
    }
    for num := range slices.Values(nums) {
        if num == 0 {
            if prev == post {
                ans += 2
            } else if abs(prev-post) == 1 {
                ans += 1
            }
        } else {
            prev += num
            post -= num
        }
    }
    return ans
}

最後 Rust 也可以用 sum() 不過要用在 iter() 後面,另外要注意的一點是 sum() 會需要指定型別,不然會有型別無法推導的問題,也可以用 nums.iter().sum::< i32>(),不過也可以用 fold 來算,像是 nums.iter().fold(0, |x, y| x + y) 這樣,就能成功推導型別。

nums.iter().sum() 的問題在於它非常泛型。sum() 方法可以將 &i32 的迭代器加總成多種不同的型別,例如 i32, i64, i128 等(任何實作了 Sum<&i32> trait 的型別)。

impl Solution {
    pub fn count_valid_selections(nums: Vec<i32>) -> i32 {
        let (mut ans, mut prev, mut post): (i32, i32, i32) = (0, 0, nums.iter().sum());
        for num in nums {
            if num == 0 {
                if prev == post {
                    ans += 2;
                } else if (prev-post).abs() == 1 {
                    ans += 1;
                }
            } else {
                prev += num;
                post -= num;
            }
        }
        ans
    }
}

2025年10月14日 星期二

ubuntu-dev-tools: /usr/bin/pbuilder-dist-simple

pbuilder-dist-simple 是個方便用來創建 pbuilder base 的 wrapper 腳本,使用的方式也很特別,就是要自己手動建立一個 symbolic link,然後它就會根據 symbolic link 的名稱來自動解析參數來處理。

例如:questing 是 Ubuntu 25.10 的 series name。

$ dpkg -S /usr/bin/pbuilder-dist-simple
ubuntu-dev-tools: /usr/bin/pbuilder-dist-simple
$ mkdir ~/bin
$ ln -s /usr/bin/pbuilder-dist-simple ~/bin/pbuilder-questing

建立完 symbolic link 就可以直接使用了,如果 PATH 有先設好的話。

$ echo $PATH
/home/user/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

$ pbuilder-questing create
W: /root/.pbuilderrc does not exist
I: Distribution is questing.
I: Current time: Tue Oct 14 06:55:39 UTC 2025
I: pbuilder-time-stamp: 1760424939
I: Building the build environment
I: running debootstrap
/usr/sbin/debootstrap
...
I: creating base tarball [/home/user/pbuilder/questing-base.tgz]
...

接下來我們可以抓 Debian libchewing source package 來試試看

$ pull-debian-source libchewing
Found libchewing 0.10.3-1 in sid
...
Downloading libchewing_0.10.3.orig.tar.xz from deb.debian.org (1.571 MiB)
[=====================================================>]100%
Downloading libchewing_0.10.3-1.debian.tar.xz from deb.debian.org (0.008 MiB)
[=====================================================>]100%

$ pbuilder-questing build libchewing_0.10.3-1.dsc
W: /root/.pbuilderrc does not exist
I: pbuilder: network access will be disabled during build
I: Current time: Tue Oct 14 07:02:09 UTC 2025
I: pbuilder-time-stamp: 1760425329
I: Building the build Environment
I: extracting base tarball [/home/user/pbuilder/questing-base.tgz]
...
dpkg-deb: building package 'libchewing3-data' in '../libchewing3-data_0.10.3-1_all.deb'.
dpkg-deb: building package 'libchewing3-dev' in '../libchewing3-dev_0.10.3-1_amd64.deb'.
dpkg-deb: building package 'libchewing3' in '../libchewing3_0.10.3-1_amd64.deb'.
dpkg-deb: building package 'chewing-tools' in '../chewing-tools_0.10.3-1_amd64.deb'.
...

$ cd ~/pbuilder/ && ls *chewing*
chewing-tools_0.10.3-1_amd64.deb          libchewing_0.10.3-1_amd64.buildinfo  libchewing_0.10.3-1.debian.tar.xz  libchewing_0.10.3-1_source.changes  libchewing3_0.10.3-1_amd64.deb     libchewing3-dbgsym_0.10.3-1_amd64.ddeb
chewing-tools-dbgsym_0.10.3-1_amd64.ddeb  libchewing_0.10.3-1_amd64.changes    libchewing_0.10.3-1.dsc            libchewing_0.10.3.orig.tar.xz       libchewing3-data_0.10.3-1_all.deb  libchewing3-dev_0.10.3-1_amd64.deb

看起來目前的 libchewing 0.10.3-1 in sid 可以在 Ubuntu 25.10 正常編譯沒有問題。