-->

網頁

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

2019/08/23

讓 less 可以顯示 ANSI 色彩

應該不少人有經驗,在使用 grep 時,要搜尋的關鍵字會用顏色 highlight 方便閱讀,例如:
$ ls -lh | grep php
-rw-rw-r--  1 zero zero  178 Aug 22 19:15 autoload.php
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 cakephp
drwxrwxr-x  5 zero zero 4.0K Aug 22 19:01 phpdocumentor
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpoffice
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpoption
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpseclib
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpspec
drwxrwxr-x  8 zero zero 4.0K Aug 22 19:01 phpunit

不過如果因為列表很長,在把搜尋結果 pipe 給 less 以後,顏色就消失了。

這其實是 grep 這邊會判斷 output 到哪一種型態的 I/O,像是後面接的是 bash pipe,grep 就會自動移除 ANSI color 的 syntax,畢竟不知道 pipe 資料給誰,如果後者不支援 ANSI color 就會變成亂碼。

如果很確定 pipe 後面的指令、工具支援 ANSI color 的指令,就可以下參數要求 grep 輸出顏色。例如:
$ ls -lh | grep php --color=always | less
-rw-rw-r--  1 zero zero  178 Aug 22 19:15 autoload.php
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 cakephp
drwxrwxr-x  5 zero zero 4.0K Aug 22 19:01 phpdocumentor
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpoffice
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpoption
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpseclib
drwxrwxr-x  3 zero zero 4.0K Aug 22 19:01 phpspec
drwxrwxr-x  8 zero zero 4.0K Aug 22 19:01 phpunit

2019/05/07

GNU grep 的 exit code 會因結果而不同

昨天寫了類似以下這樣一段 script:
#!/usr/bin/env bash

set -e

R=`ls /dev/ | grep sd`

echo "Results found:"
echo $R

照理來說,不管 grep 是有有撈到資料,至少會印出「Results found」字樣,但實際執行時卻什麼資料都沒有輸出。

後來使用「bash -xv」來執行,監視值流程,才發現 script 執行到一半就中斷了:
$ bash -xv qwe.sh 
#!/usr/bin/env bash

set -e
+ set -e

R=`ls /dev/ | grep sd`
++ ls /dev/
++ grep sd
+ R=

追蹤後發現二個結果交互影響導致 script 中斷:
  • set -e 的設定
  • grep 的 exit code


在 bash 中「set -e」代表遇到錯誤立即中斷執行;而 grep 的 exit code 比較令人意外,當 grep 有找到資料時,則 exit code 為 0 (正常結束),若 grep 都沒有找到指定的字串,則會回傳 1 (錯誤)。這二件事情同時發生,所以就導致了上面的 script 在 grep 執行後就中斷執行。


// ------



shell script 好像沒什麼 debug 的工具,不過在執行時可以透過 bash 的參數,來提供執行時的一些狀態,例如: -xv。

2019/05/06

在 bash 看指令執行後的 exit status code

老題目了,做個筆記。

Linux 底下,所有程式、指令結束都會有個 exit code,有點像是執行成功、或失敗的狀態。一般來說,正常執行的 exit code 都會是 0。

如果要看前一個指令執行後的 exit code,可以使用「echo $?」來看:
$ cd .
$ echo $?
0
$ mkdir app
mkdir: cannot create directory ‘app’: File exists
echo $?
1

2018/12/26

jq (JSON parser) 處理含有 "-" (dash) 的 key 名稱

爬 log 剛好遇到 JSON 的其中一個 key 名稱中有「-」(dash) 符號,所以 console 怎麼寫都有錯誤訊息:
$ cat log | cut -d$'\t' -f 4 | jq   .http-post
jq: error: post/0 is not defined at , line 1:
.http-post
jq: 1 compile error

從錯誤訊息可以看到「http-post」被切斷了。

若 key name 有特殊符號,或是特殊字元,記得要用引號包起來進行查詢:
$ cat log | cut -d$'\t' -f 4 | jq '."http-post"'
{
  "action": "get",
  "name": "John",
  "category": "RD",
}

2018/09/28

Increase Bash History Size

It is really convenient to use Ctrl+R to find often used commands.

If Bash history is not enough to save those commands, try to add ENV vairables below into .bashrc:
# amount of commands you want to store in .bash_history
export HISTSIZE=1000

# amount of commands you want to store in current bash session
export HISTFILESIZE=200000

Reference:

2018/08/27

type (in Bash) 來判別 shell 實際執行的命令

Linux 的 shell 提供了很多彈性,讓使用者可以客製化自己的工作環境。但也因此有時候把環境搞亂了自己也沒發現。

例如說:
johnroyer@box:~$ phpunit --version
PHPUnit 7.3.2 by Sebastian Bergmann and contributors.

johnroyer@box:~$ cd devel/laravel/
johnroyer@box:~/devel/laravel$ phpunit --version
PHPUnit 6.5.12 by Sebastian Bergmann and contributors.

這個時候雖然可以使用 which 來找出實際上執行的指令是哪一個 binary,但有時不一定與執行的是同一個。例如,上面的環境中,執行 which 都會有相同的結果:
$ which phpunit
/home/johnroyer/.config/composer/vendor/bin/phpunit

如果使用 type (Bash built-in) 來檢查的話,則會更清楚的告訴你,指令會如何執行,例如:
$ type phpunit
phpunit is a function
phpunit () 
{ 
    REPO_PHPUNIT=`pwd`"/vendor/bin/phpunit";
    if [ -e $REPO_PHPUNIT ]; then
        $REPO_PHPUNIT $*;
    else
        /home/johnroyer/.config/composer/vendor/bin/phpunit $*;
    fi
}

which 是由 $PATH 環境變數中的路徑來找出對應的執行擋路徑;type 則是由 Bash 當下的環境去檢查當下到底會如何執行。

2016/09/27

grep 時保留前後 N 行內文

一般 grep 只會將出現關鍵字的那一行文字顯示出來,例如:

johnroyer@box:~/logs$ zgrep 'parse' *gz
2016-02-27.log.gz:[2016-02-27 12:00:35] local.INFO: DOMDocument cannot parse XML: Premature end of data in tag html line 2
2016-02-27.log.gz:[2016-02-27 12:00:36] local.INFO: DOMDocument cannot parse XML: Premature end of data in tag html line 2
2016-02-27.log.gz:[2016-02-27 12:01:08] local.INFO: DOMDocument cannot parse XML: Premature end of data in tag html line 2
....

但有時顯示出來的訊息只是 function call stack trace 的其中一行,單看這一行無法理解到底發生了什麼事情。

遇到這種情況時,可以透過參數「-A」和「-B」來設定保留前後文:

johnroyer@box:~/logs$ zgrep 'Exception' *.gz -A 5 -B 2
[2016-05-29 23:41:22] production.INFO: RuntimeException: https://theinitium.com/newsfeed/
[2016-05-29 23:41:22] production.INFO: DOMDocument cannot parse XML: PCDATA invalid Char value 8
[2016-05-29 23:41:23] production.ERROR: exception 'RuntimeException' with message 'Invalid host label, check its content' in /home/segm/prod/www-crawler/vendor/league/url/src/Components/Host.php:164
Stack trace:
#0 /home/segm/prod/www-crawler/vendor/league/url/src/Components/AbstractSegment.php(47): League\Url\Components\Host->validate('rss_Content.jsp')
#1 /home/segm/prod/www-crawler/vendor/league/url/src/Components/AbstractSegment.php(39): League\Url\Components\AbstractSegment->set('rss_Content.jsp')
#2 /home/segm/prod/www-crawler/vendor/league/url/src/Components/Host.php(72): League\Url\Components\AbstractSegment->__construct('rss_Content.jsp')
#3 /home/segm/prod/www-crawler/vendor/league/url/src/AbstractUrl.php(226): League\Url\Components\Host->__construct('rss_Content.jsp')
.....


上面的範例是關鍵字前保留 2 行,往後保留 5 行。

2016/07/15

Helper for PHPUnit path detection in bash

較新的 PHP 專案都會在 composer require_dev 自帶 phpunit,這個時候要執行 phpunit 都應該要使用專案中設定的 phpunit 版本:
$ cd /path/to/repository
$ vendor/bin/phpunit

若該專案沒有設定 phpunit 時,才使用系統上,或是 composer global 的 phpunit:
$ cd /path/to/repository
$ ~/.composer/vendor/bin/phpunit  # or "phpunit" for system global

不過這實在有點麻煩,所以乾脆寫 script 處理掉:
phpunit() {
   REPO_PHPUNIT=`pwd`"/vendor/bin/phpunit"

   if [ -e $REPO_PHPUNIT ]; then
      echo "... Run by vendor/bin/phpunit ..."
      $REPO_PHPUNIT $*
   else
      ~/.composer/vendor/bin/phpunit  $*
   fi
}

這樣一來,只要執行 phpunit 就會自動去檢查專案底下是否有 phpunit 可以用;若沒有則自動使用系統的 phpunit。

2016/02/04

xargs -P 在 stdout 可能會遇到 race condition

爬 log 發現 log 格式不正確,而且還是經常發生,而手動追蹤時又找不到錯誤在哪裡:
find . -name '*2016-01*.log.gz' | xargs -I'{}' -P 4 zgrep keyword {} | awk ...

做了測試以後才發現 xargs -P 時,各個 process 只要有 stdout 就會和其他 process 打架,造成資料還沒寫完就被其他 process 插單,導致最後出來的資料不正確。



先建立二個檔案,儲存不同的二個資料。

0.test.log (每行 50 字):
.................................................
.................................................
.................................................
....

1.test.log (每行 50 字):
1111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111
...


接下來使用 xargs 來 echo 這二個檔案內容:
find . -name '*test.log' | xargs -I'{}' -P 2 cat {} > output.xargs.log

接下來寫個 script 來檢查 output.xargs.log 的內容是否都正確:
for LINE in `cat output.xargs.log `; do
    if [ 50 -lt ${#LINE} ]; then
        echo $LINE
    fi
done

結果會發現 output 有一行超過 50 個自得情況發生:
111111111111111111111111111111111111.................................................

而相同的情況下,parallel 就不會有相同的情況發生:
find . -name '*test.log' | parallel -j 2 cat {} > output.xargs.log

原因是 parallel 會將 jobs (process) 的 output 先 buffer 起來,等到整個 job 都結束以後在一起送到 stdout。若使用上述的範例改用 parallel 的操作來測試的話,可以發現不同 job 的 output 有被完全區隔開來,沒有混在一起:

...
.................................................
.................................................
.................................................
1111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111
...


總之,以後用到 xargs -P 時,要小心 race condition ... (暈)

2015/11/10

特定情況下 PHP 可以比 awk 還要快

因為工作上的需要,會需要將 HTTP log 抓出來做統計,所以會遇到類似下方的 RESTful path:
GET /user/123/bio HTTP/1.1 ...
GET /user/456/bio HTTP/1.1 ...

如果取完整的 path 則無法辨別後端到底是使用哪一個 API,所以使用 awk 的 regex 辨識後 mapping 到 API 名稱上。寫完以後的 awk script 大約有 300 行左右,一份 log 大概要花 2 分鐘左右。

後來經高人指點,PHP 的 native library 好歹也是 C++ 寫的,理論上不會太慢,於是用 PHP 的 preg_match() 將相同的邏輯寫了一次。同一份 log 使用 PHP 來 parse 大約只需要 1 分 32秒。

另外 PHP 預設會載入已安裝的 extensions (mysql, mcrypt ...),既然只用到 native library 的話,其實這些也可以去掉不要用。改為「php -n」不載入任何 extension 再執行時,速度又快了一些,只要約 1 分鐘。

PHP 其實還有一些可以繼續調整的東西,像是把資料放在陣列裡面做搜尋時,如果把資料存成 array index 並使用 array_key_exists() 方式去判斷,會比 in_array() 還要更快 [Ref]

2015/09/16

寫 shell script 的一些平行處理工具

最近在處理數十 TB 的 HTTP server log 有感,記錄一些可以拿來做分散式運算的工具以及語法 (參數)。



gzip 一直是你的好工具,特別是儲存空間放在網路上時,可以大幅的減少讀取、寫入資料時所需要的 throughput。不過 gzip 一次只會用到一個 CPU core 壓縮資料,所以有人寫了 pigz ,在壓縮時建立多個 thread 同時運算。

還有不少針對 gz 的工具可以使用,像是:zcat、zgrep 和 zless (這超神奇,其實打 less 好像就會自動偵測是不是 gz 了  XD)


parallel


之前的筆記參考一下即可。


sort


排序大量資料也是會耗掉相當多的時間,幸好 sort 內建平行運算功能,只要加個參數即可。

$ sort --parallel=8 -S 4G unsort.list > sorted.list

「--parallel」可以指定要同時多少資源做排序,而「-S」則是設定要使用多大的記憶體來做排序。



xargs


感謝 Joe Horn 和 Wen-Shih Chao 提供指點,xargs 也有 parallel 的功能。使用「-P」參數就可以讓 xargs 自動做平行處理。

ls *.log | xargs -P 8 grep PATTERN




目前最常用的是這幾個,其他的就待以後用到慢慢補上。若有更好的做法也歡迎分享~

2015/08/03

多核心主機搭配 GNU parallel

假如要將 apache log 中,包含某個 pattern 的記錄找出來,寫起來大概會像這樣子:
cat apache*.log | awk -f log-parser.awk

舊電腦就是放著一個檔案一個檔案慢慢跑,沒什麼問題。若新電腦現在不少都是多核心,看個 parser 慢慢跑,但是剩下的 3 個 CPU core 和 disk 都悠哉沒事做,感覺在浪費時間。

parallel 專門設計來讓 script 可以同時並行運作,使用電腦資源來節省時間的好工具。

假設今天 awk 非常吃 CPU 使用量 (規則較為複雜),處理一個檔案會花上很多 CPU 時間,則可以將 log 中的內容分散給多個 CPU 同時處理:
cat apache*.log | parallel --pipe awk -f log-parser.awk

此時,parallel 偵測到有 4 CPU cores,則會自動將 $FILE 內容分批轉送給 awk 處理 (一行為一個單位),這樣能讓 4 個 CPU 同時跑 awk 解析 log 內容。

不過以一行為一個單位轉送資料給 awk,也會耗掉一些運算資源,可以透過 --block 來要求 parallel 多少資料當作一個單位來轉送資料。以下假設一次送 10MB 的資料給 awk 處理:
cat apache*.log | parallel --pipe --block 10M awk -f log-parser.awk

倘若今天 awk 的要處理的東西並不複雜,可以輕鬆解決掉,上面的寫法反而會讓 CPU 閒閒沒事做,不如就讓一個 awk 負責處理一個檔案,且多個檔案同時進行。此時就可以讓檔案處理的部分轉交給 awk:
parallel --pipe -u --block 10M awk -f log-parser.awk ::: apache*.log

備註:parallel 預設會在所有工作執行結束才輸出結果,若要讓 parallel 即時將結果印出,則可加上參數「-u」。

另外一點要注意的是,parallel 預設會使用所有的 CPU 來處理工作,這在多人共同使用的主機上並不是一件好事,一跑下去大家都不用做事了。所以若在共用環境上請記得加上 -j (jobs) 參數,來限制 parallel 不要用掉所有的系統資源。


Reference:

2014/01/15

在 grep 搜尋「-」符號

用 grep 搜尋檔案中出現的文字,通常會這樣寫:
$ grep STR fileToSeatch

不過當要搜尋的字串有特出符號,像是「-」,就會出現錯誤訊息:
$ grep "->getVal()" *
grep: invalid option -- '>'

原因是 grep 將「-」開頭的字串視為命令選項 (command option),去尋找「>get()」這個選項,而這個選項並不存在,所以導致錯誤。

在命令輸入前加上「--」,表示之後的參數都當作一般輸入而非選項 (end of option):
$ grep -- "->getVal()" *

要搜尋的字串,就算沒有空白,也建議用引號括起來。像是「>」、「&」等,沒加上引號會在 bash 解析時就發生錯誤:
$ grep > *  # shoud be: grep ">" *
bash: *: ambiguous redirect

2013/08/13

Array Iterate in Bash

Bash 裡面有個 for-in 可以很輕鬆的對多筆資料做迭代,不過我語法一直弄錯。

文字列表:
LIST="item1 item2 item3"

for ITEM in $LIST; do
   echo $ITEM
done


如果是陣列,語法要換:
LIST=(  \
  item1 \
  item2 \
  item3 \
)

for ITEM in ${LIST[@]}; do
  echo $ITEM
done


Reference:
Bash For Loop Array: Iterate Through Array Values

Bash Guide for Beginners - Chapter 10. More on variables

2013/07/09

Alias Command Contains Spaces in Bash

.bashrc 下設定 command alias 的方法是:
alias svm='svn'   # 我常常手殘

不過假設要 alias 的指令包含空白,如「svn commit」要 alias 成「svn commit --editor-cmd vim」,就得靠自訂 function 了。

新增一個 function 蓋掉原本的 svn 指令,如果 svn 後接的參數是 commit,便加上 --editor-cmd:
svn(){
   if [[ $@ == commit ]]; then
      command svn commit --editor-cmd vim
   else
      command svn "$@"
   fi
}

不過 svn commit 有時還會有其他參數,像是檔案路徑等,所以這樣寫還是會發生意外。要把參數判斷要改,順便在執行指令的時候把參數也塞回去:
svn(){
   if [[ $@ == commit* ]] || [[ $@ == ci* ]]; then
      command svn "$@" --editor-cmd vim
   else
      command svn "$@"
   fi
}