弄清楚 >/dev/null 2>&1 與 2>&1 >/dev/null 的差異

以前都是硬背起來的,如果要把所有的輸出都丟到 /dev/null,要用前面的方式才會是對的,但其實在 bash 的 manpage 裡面有提到:

Note that the order of redirections is significant. For example, the command

    ls > dirlist 2>&1

directs both standard output and standard error to the file dirlist, while the command

    ls 2>&1 > dirlist

directs only the standard output to file dirlist, because the standard error was duplicated from the standard output before the standard output was redirected to dirlist.

這是因為照順序跑 dup2() 的關係 (參考 bash 的 redir.c)。

前者會先把 dirlist 開起來後透過 dup2() 複製到 fd=1,再用一次 dup2() 把 fd=1 複製到 fd=2,達到我們要的效果。

後者則是先把 fd=1 複製到 fd=2 (於是大家都丟到 stdout 了),接下來再把 dirlist 開起來,丟到 fd=1。這樣變成 ls 寫到 stderr 的東西變成到 stdout 了,而本來寫到 stdout 的東西進到 dirlist 這個檔案裡。

剛剛寫 code 時想找一下有沒有官方文件或是 source code 有描述 ordering 或是有定義行為,結果發現 bash 的 manpage 裡面就有提到了...

FreeBSD 把 root 預設的 shell 改成 /bin/sh

Hacker News 首頁上看到 FreeBSD 把 root 的 shell 從 /bin/csh 換成 /bin/sh 了:「sh(1): make it the default shell for the root user」,Hacker News 上的討論也可以看一看:「FreeBSD switches the default root shell from csh to sh (freebsd.org)」。

依照說明是把 FreeBSD 的 /bin/sh 加上了不少東西,所以算是堪用了:

In the recent history sh(1) has gain the missing features for it to
become a usable interractive shell:
- command completion
- persistent history support
- improvements on the default bindings in emacs mode
- improvements in the vi mode (repect $EDITOR)
- print a newline when exiting via ^D
- default prompt and improvements on how PS1 can be configured
- and more.

This changes also simplifies making tiny freebsd images with only sh(1)
as a shell

以前也是用 csh 系列的 (像是 tcsh),後來開始用 Linux 就往 BashZsh 定居了...

SSH 對傳入參數的 quoting

昨天在 Hacker News 首頁上看到「SSH quoting」這個,看得出來作者被 OpenSSH 玩弄到不要不要的樣子...

先簡單的整理一下:

$ ssh example.com 'cd /tmp; pwd'
/tmp
$ ssh example.com 'bash -l -c "cd /tmp; pwd"'
/tmp
$ ssh example.com bash -l -c "cd /tmp; pwd"  
/home/gslin

第三個指令發生的「預期外的行為」,但寫習慣的人會把指令全部包成一個字串,就很自然的避開這個問題了。當然 OpenSSH 的設計 (讓你不用加 quote 也會動) 的確也是容易中獎的點啦...

原來 Sentry 有支援 Bash...

Sentry 可以拿來蒐集自己開發軟體的錯誤事件,剛剛突發奇想用搜尋引擎找了一下,發現 Sentry 是有支援 Bash 的:「Sending Sentry Events from Bash」。

文章裡面也直接提到了 hook 的用法,在裝了 sentry-cli 後可以掛進來:

#!/bin/bash
export SENTRY_DSN=
eval "$(sentry-cli bash-hook)"

自己架 Sentry 的步驟我有丟在「Sentry」這邊,或者也可以用雲端的版本。

丟給同事參考看看...

Bash Script 的好習慣

這篇給了一份 bash script 用的 tempalte,但更重要的反而是裡面提到的 best practice:「Minimal safe Bash script template」。

首先是不要寫 /bin/bash 這件事情,因為有些系統是沒有 /bin/bash 的,像是 FreeBSD

如果程式是可以用 POSIX sh 語法的話,應該優先考慮 /bin/sh,如果用到非 POSIX 標準的語法的話,用 env 帶出來會少一些問題:

#!/usr/bin/env bash

再來是 fail 時就趕快停止,不要再往下執行,這點算是老生常談了,文章作者也有給一個範例說明:

set -Eeuo pipefail

再來另外一個還蠻有用的事情是攔下常見的 signal 處理「後事」:

trap cleanup SIGINT SIGTERM ERR EXIT

cleanup() {
  trap - SIGINT SIGTERM ERR EXIT
  # script cleanup here
}

其他的可以看一看,但未必要全盤收下...

在 Bash 的迴圈裡面跑 FFmpeg

Bash 的迴圈裡面跑 FFmpeg 有時候會遇到奇怪的靈異現象,發現是這個問題:「execute ffmpeg command in a loop」。

原因是當你用 while + read 產生迴圈時會有對 stdin 的操作行為,而 FFmpeg 預設也會去讀 stdin (WTF),於是兩邊就打架了。

解法是用 -nostdin 叫 FFmpeg 不要手賤去讀 stdin,這樣就可以解決這個問題。

第二堂:「Shell Tools and Scripting」

這個系列是從『MIT 的「The Missing Semester of Your CS Education」』這邊延伸出來的,這篇文章講第二堂課「Shell Tools and Scripting」。

這堂有點像是第一堂的延伸,在講更多 shell 的操作與工具,然後說明 shell script 怎麼寫。

開頭就先說明有 function,然後講了不少 magic variable,像是 $0$1$9,而 $@$# 也提到了 (但居然沒提到 $*),然後再來是 $$!!$_

然後提到 true 與 false,接著就講條件 || 與 && 了。後面就開始講 shell 裡面的 for 與 if,基本上到這邊已經能寫不少東西了?

後面就介紹更多工具...

Idempotent Bash Script

看到「How to write idempotent Bash scripts」這篇,重點在講 Idempotence,這個詞是從數學上借來的,講重複操作的不動性:

An element x of a magma (M, •) is said to be idempotent if:

x • x = x.

If all elements are idempotent with respect to •, then • is called idempotent. The formula ∀x, x • x = x is called the idempotency law for •.

在 CS 領域也是一樣的概念:

Idempotence (UK: /ˌɪdɛmˈpoʊtəns/, US: /ˌaɪdəm-/) is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.

而這篇講的是 Bash 上有些常見的行為要怎麼改成 Idempotence:

It happens a lot, you write a bash script and half way it exits due an error. You fix the error in your system and run the script again. But half of the steps in your scripts fail immediately because they were already applied to your system. To build resilient systems you need to write software that is idempotent.

一個常見的例子是 cron job 是否可以重複執行的問題。

如果 cron job 裡的程式都是 idempotent,那麼就不需要擔心重跑會因為前一隻 script 產生的環境而失敗,導致無限循環而需要人介入...

另外一個更進階的是同時有兩個 process 在執行同一個 script (可能在不同機器上),這也是要考慮的問題,不過這個問題在大多數情況下有各種 lock 系統可以協助避免,應該不是太大的問題...

Bash 裡處理 PID file 的方式...

看到「Age comparison in Bash for files and processes」後查了一些資料,如果在不使用外部程式處理的話,的確是多做了不少事情。

這是 Bash-ot 的說明:

file1 -ot file2
       True if file1 is older than file2, or if file2 exists and file1 does not.

而這是 test (也就是 [ 這隻程式) 對 -ot 的說明:

FILE1 -ot FILE2
       FILE1 is older than FILE2

多了檔案是否存在的檢查...

另外可以參考「What's the difference between [ and [[ in Bash?」這邊的說明,Bash 的 [[ 是 Bash 特有的加強版,而 [ 則是用系統的 test 運算。

Bash Handbook (更像長篇的 Cheatsheet?)

在「Bash Handbook」這篇看到了有人在 GitHub 上整理了「For those who wanna learn Bash」。

說是 Handbook,更像長篇的 Cheatsheet,帶了基本的說明讓有背景的人可以馬上了解要怎麼用...

另外因為 Bash 是很多 shell 分支的起源,其實有不少東西對應到 Zsh (以及其他 shell) 也是會通的,拿來複習也是不錯 :o