顯示具有 bash 標籤的文章。 顯示所有文章
顯示具有 bash 標籤的文章。 顯示所有文章

2013年7月18日 星期四

shell script: 用 eval 執行內含單引號的變數

某些情況下需要在指令裡加入單引號, 但是會有問題。下面是一個簡化後沒意義的例子:

cmd="ls '/etc'"
echo "> Run $cmd"
$cmd

執行結果如下:

> Run ls '/etc'
ls: cannot access '/etc': No such file or directory

但是實際在 bash 上打入 ls '/etc' 卻沒問題。從這裡得知, 解法是用 eval 執行:

cmd="ls '/etc'"
echo "> Run $cmd"
eval $cmd

另外記得除錯時可以配合 set -x 顯示 bash 實際執行的指令, 或是用 bash -x 執行 script。

2011年5月4日 星期三

expr 充當簡單 regexp 工具

有時候會需要用一下簡單的 regexp 處理字串, 以前我會因此用 perl -ne ..., 後來太久沒寫 perl 又忘了語法, 遇到這種情況就覺得頗困擾的。今天在讀別人的 shell script 時發覺 expr 竟然也有基本的 regexp 功能!!

以下是 man expr 的部份內容:
STRING : REGEXP
Pattern matches return the string matched between \( and \) or null; if \( and \) are not used, they return the number of characters matched or 0.
附上相關例子:
$ expr "hello world" : "hello.*"
11
$ expr "hello world" : "hello"
5
$ expr "hello world" : "hello \(.*\)"
world
有了 expr, 以後湊指令方便多啦

2010年12月31日 星期五

自制彈性的 bash prompt + 解決問題的流程

最近開了幾個 branch 要切來切去, 每次還要下「hg branch」確認狀態, 有點麻煩。就找了一下有沒有人提供啥方案, 這種常見的痛苦, 應該早有人解掉了。結果找到 Prompt Extension, 完全符合我的需求, 官網文件很清楚, 設定也簡單。

滿足需求後想知道它怎麼做的, 關鍵在於如何每次執行 hg 指令就檢查並更新 prompt。只有用 bash 提供的 API 才能正確地更新 prompt, 其它手段應該都是無效的。翻了一下 Prompt Extension 原始碼, 發覺它並沒有 hook 掉 hg 的各個指令。於是我猜是 hg update 本來就會換 prompt, 改看 mercurial 原始碼。看了一陣子覺得不像, 於是回頭交叉比對看看, 是誰改了 prompt。

試了關掉 prompt / 打開 prompt / 不同 hg 指令, 最後發覺....... bash 每執行完一個指令就會重設 prompt 啊!! 從 Prompt Extension 學到的是, 設 PS1 時可以搭配單引號內加函式, 這樣每次更新 PS1 時, 可用函式視情況顯示不同的訊息。之前都用雙引號, 所以只有一次的效果。

換句話說, Prompt Extension 做的事是:
  • 提供設 PS1 的範例。
  • 提供一個 hg 指令來 parse 格式化字串, 藉以顯示不同的 repository 訊息。
總結此次解題的成果:
  • 判斷問題有解: 成功。
  • 背景知識: 知道只有 bash 提供 API 才有機會, 算是成功。
  • 猜測解決的大概方向: 失敗。明明有了正確的知識, 結果卻先猜 Prompt Extension 解的。
  • 第一次修正猜測方向: 失敗。一廂情願地猜到 mercurial 身上, 急著跳下去解, 卻沒先確認這個假設。
  • 回頭驗證假設: 成功。搭配背景知識, 找到正確的原因。
一開始猜的方向錯誤, 繞了一圈才找到做法。幸好我有提早回頭確認一開始的假設, 省了一些冤枉路。愈來愈熟悉「假設 -> 驗證」的樹狀解題流程, 漸漸抓到根本的解題方法, 再來就是增加經驗, 累積背景知識了。這種系統化的解題流程應該是大學時要學會的事, 結果到 26 歲才開竅, 唉呀呀。

2010年9月25日 星期六

寫 alias 切換路徑到 virtualenv 相關目錄

用 virtualenv 時常會需要切入 site-packages 或 src 內做些事, 久了就覺得很麻煩。雖然 virtualenvwrapper 有提供相關指令, 但又不想為了這件事而多裝 virtualenvwrapper, 它的其它功能對我來說幫助不大, 加上我自己有個 script, 用來安裝開發用的 virtualenv, 懶得整合那個 script 和 virtualenvwrapper 的路徑 (也就是指定裝到 ~/.virtualenvs), 所以決定自己寫切換路徑的 alias。

Python 沒提供堪用的單行寫法, 但為此回頭學 awk, sed 也太麻煩了。想想還是回頭寫 Ruby 吧, 相較於 Perl, 至少較熟一些 Ruby 。

alias cdve='cd $(echo $PATH | ruby -ne '"'"'puts split(":")[0].split("/")[0..-2].join("/")'"'"')'
alias cdvesrc='cd $(echo $PATH | ruby -ne '"'"'puts split(":")[0].split("/")[0..-2].join("/")'"'"')/src'
alias cdvesite='cd $(echo $PATH | ruby -ne '"'"'puts split(":")[0].split("/")[0..-2].join("/")'"'"')/lib/python2.5/site-packages/'

如上所示, 進入 virtualenv 後, 打 cdve / cdvesrc / cdvesite 會切到 virtualen 的根目錄、src、site-packages。

其中最難搞的是在單引號內用單引號, 參考 《BASH, escaping single-quotes inside of single-quoted strings》, 用 "'" 來表示單引號, 總算是搞定了。關鍵在於用到單引號時就換成 "'", 但字串間不能有空白, 結果就變成超難懂的 bash code 了。

2010年4月22日 星期四

幫 python -m 加上 auto completion

完整程式在這裡, 設好 py_dist_path 和 py_dist_name, source 這個 bash script 就OK 了。比方說你自己寫的 package root 是 mypkg, 放在 /home/fcamel/mypkg:
py_dist_path=/home/fcamel/mypkg
py_dist_name=mypkg
source ~/.bash_completions/python_module

之後打 python -m mypkg 後就可以按 tab 補完。

注意, 我直接覆蓋原本 python 註冊的 complete function, 需要的話到 /etc/bash_completion 裡撿出 _python() 再把它們拼在一起吧。

An introduction to bash completion 將運作原理介紹得相當清楚, 看完就會做了。關鍵在於了解 bash 和 complete 的溝通方式。man bash 裡也有相關訊息, 不過直接啃還是太硬了。

2010年1月18日 星期一

/bin/rm: argument list too long 的解法和原因

若不小心在一個目錄下產生太多暫存檔, 直接 rm -f *.tmp 會出現這個錯誤訊息:
/bin/rm: argument list too long
若不幸的同一目錄下還有其它檔案, 不能一次砍掉整個目錄,這時可以透過 find 和 xargs 解掉這問題:
find . -name "*.tmp" -print0 | xargs -0 rm
這樣就會透過 xargs 把檔案名稱傳給 rm。注意,find、xargs 分別加上 -print0 和 -0 是為了避開檔名中含有空白字元, 詳情請 man find 並閱讀 -print0 的部份。

這篇這篇解釋了為什麼有這個問題, 以及為何 find + xargs 可以避開它。問題的源頭不是 bash 也不是 rm, 而是 kernel 執行命令時會先將參數存到 buffer 裡。buffer 的大小是 MAX_ARG_PAGES * "page 大小" --- 一般而言是 32 * 4k = 128k。

那為啥 xargs沒這問題呢? 對照 man page 的說法, 以及粗略地 trace過 xargs 的原始碼後, 發覺 xargs 自己有管一個參數 buffer, 一但 buffer 滿了, xargs 就會執行傳給它的指令和起始參數, 接著清空 buffer, 再繼續讀剩下的參數。換句話說, xargs 會分多次執行完所有參數。假設要刪除的檔案是 1, 2, ..., 100000000。xargs 有可能這麼做:
rm 1 2 ... 1000000
rm 1000001 1000002 ... 2000000
...
我沒有算 buffer size 和檔名順序, 上面只是示意用的例子。


Update 

Victor 說 find 加上參數  -delete 就可以了, 即:
find . -name "*.tmp" -delete
那........ 以上的說明, 就當作 find + xargs 的靈活應用之一吧。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...