2013年12月6日 星期五

Bash 內建的 getopts 與外部的 getopt 的使用方式

通常在 GNU/Linux 系統底下使用 command 時都會有帶一些額外的參數,像是以下的例子:

$ echo `seq 0 9` | xargs -n1 | wc -l

利用 `seq 0 9` 產生出 0 到 9 的數字,然後 echo 輸出,再用 xargs -n1 利用空白分開每個數字插入換行符號,然後再導到 wc -l 去計算行數。(其實寫成 `seq 0 9 | wc -l` 也有同樣的效果,利用 hexdump -C 就可以看出其中的不同)

其中的 wc -l 也可以寫成 wc --lines 這樣更清楚、更容易理解。

如果說我們想要利用 Bash shell 寫一個同樣的功能,就可以寫成下面這樣:

#!/bin/bash

while getopts l name; do
    case "$name" in
        ('l')
            line=1
            ;;
    esac
    shift
done

if [ -n "$line" ]; then
    awk 'END { print NR }' "$@"
fi

跟原本的 wc -l 提功同樣功能只是換成使用 awk 這個指令來計算行數。

然而 Bash 內建的 getopts 無法直接提供 --lines 這樣的使用方式,於是就可以改用外部的 getopt 指令來達成。

#!/bin/bash

set -e
eval set -- $(getopt -o l -l "lines" -- "$@")
set +e

while :; do
    case "$1" in
        ('-l'|'--lines')
            line=1
            ;;
        ('--')
            shift
            break
            ;;
    esac
    shift
done

if [ -n "$line" ]; then
    awk 'END { print NR }' "$@"
fi

如果說要利用 Bash 內建的 getopts 寫一個 xargs -n1 這樣的功能,則可以寫成以下這樣:

#!/bin/bash

while getopts n: name; do
    case "$name" in
        ('n')
            max="$OPTARG"
            ;;
    esac
    [ "$OPTIND" = 2 ] && shift
    [ "$OPTIND" = 3 ] && shift 2
done

if [ -n "$max" ]; then
    awk "{for (i = 1 ; i <= NF ; i++) { if (i % $max == 0) printf \"%s\\n\", i; else printf \"%s \", i } }" "$@"
fi

如果要支援 xargs --max-lines=1 這樣的使用方式,則可以利用外部的 getopt 改寫成以下這樣:

#!/bin/bash
# filename: xargs.sh

set -e
eval set -- $(getopt -o "n:" -l "max-lines:" -- "$@")
set +e

while :; do
    case "$1" in
        ('-n'|'--max-lines')
            max="$2"
            shift 2
            ;;
        ('--')
            shift
            break
            ;;
    esac
done

if [ -n "$max" ]; then
    awk "{for (i = 1 ; i <= NF ; i++) { if (i % $max == 0) printf \"%s\\n\", i; else printf \"%s \", i } }" "$@"
fi

這樣就可以使用 `xargs.sh --max-lines=1` 了。

張貼留言