めもぶろぐ

お勉強したこと、趣味なんかを適当に書いてます。。。

bash-completionのカスタマイズ・自作

bash-completionってなんて便利なんだろう。。。
シェルスクリプトを作成していて、引数をよく使うのですが、どれ使えばいいんじゃい
となってしまったときタブ補完できたらなあ



なんて思い、bash-completionを調べました。
とりあえずどこに有るかは、下記コマンドを実行してみてください。


updatedb && locate bash-completion

#
# [実行結果] 1行抜粋
# /usr/share/bash-completion/completions/vgextend
#


ということで、/usr/share/bash-completion/completionsにありました。



addpartというやつが一番単純そうなので、これを参考に自作補完機能を実装します。
ログインプロファイルにでも自作補完機能を配置しましょう。


_addpart_module()
{
	local cur prev
	COMPREPLY=()
	cur="${COMP_WORDS[COMP_CWORD]}"
	case $COMP_CWORD in
		1)
			local DEVS=''
			while read dev; do DEVS+="$dev " ; done < <(lsblk -pnro name)
			OPTS="--help --version $DEVS"
			COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
			;;
		2)
			# FIXME: how to determine next free partition number
			;;
		3)
			COMPREPLY=( $(compgen -W "start" -- $cur) )
			;;
		4)
			COMPREPLY=( $(compgen -W "length" -- $cur) )
			;;
	esac
	return 0
}
complete -F _addpart_module addpart

addpartは上記のような感じ。
配列に引数を格納していってそれを表示させる感じです。



試しに自作completionとシェルを作ってみます。



/var/log/message向けに時間を絞り込んでログを出力するシェルをつくります。

CentOS7から、もしくは他のディストリビューションでは
journalctl --sice=12:00 とかで対応できるので、全く意味のないシェル。





お試しシェル1号

#!/bin/bash

argv=()
while ([ ${#} -ne 0 ]); do
  case "${1}" in
    --*)
      if [[ "${1}" =~ "start=" ]]; then sTime=$(echo "${1}" | cut -d= -f2); fi
      if [[ "${1}" =~ "end=" ]];   then eTime=$(echo "${1}" | cut -d= -f2); fi
      shift
      ;;
    *)
      argv=("${argv[@]}" "${1}")
      shift
      ;;
  esac
done

awk -v sTime=${sTime} -v eTime=${eTime} '{
  if (sTime <= $3 && $3 <= eTime) {
    print
  }
}' ${argv[0]}

この時点では当然タブで補完ができない。
実行するときは下記のような指定方法が必要。


なお、引数は順不同。



/root/work/ExtractLog.sh /var/log/messages --start=22:00 --end=22:10


で、ここでの問題点は引数に--startやら--endやらが必要であること。
引数のオプションなんて忘れるし、順番も忘れる。




まあ、--foo --barって指定できると便利なことも有るのだけど。




補完の設定をしましょう。
.bash_profileに追記しましょう。
もしくは/etc/profile.d/user_bash_completion.shとかいう感じで新規作成もよし。




タブ補完向けの関数作成

_exlog_complete() {
  local cur
  COMPREPLY=()
  cur="${COMP_WORDS[COMP_CWORD]}"

  ORG=$IFS
  IFS="
  "
  OPTS=("--start=" "--end=")
  COMPREPLY=( $(compgen -W '${OPTS[@]}' -- $cur) )

  IFS=$ORG
  return 0
}
complete -F _exlog_complete -o default ExtractLog.sh

"complete -F 関数名 -o default 補完機能を実行するときの対象になるコマンド"


"-o default" はファイルとかディレクトリを補完時に出力してくれます。
ファイル・ディレクトリ名の補完が不要のときはつけなくて良いです。




今回はログファイル名の指定をするようにしているので、オプションを入れています。





末尾のコマンド名はシェル名でも良いです。なので下記のようにして
複数シェルにたいして補完機能が効くようにしても便利です。



Command=$(find /root/work -type f -name "*.sh" -exec basename {} \;)
complete -F _exlog_complete -o default ${Command[@]}


できあがったので、ログインし直してシェル実行してみます。

#
# まずはシェル名を指定する
#

[root@localhost work]$./ExtractLog.sh

#
# タブを押して見る。
#

[root@localhost work]$./ExtractLog.sh --
--end=    --start=

#
# おお、共通部分まで保管してくれた。
# eを押してタブを押して見る。
#

[root@localhost work]$./ExtractLog.sh --end=


できた。。。

おわり

bashのターミナルに時間を表示させる

プロンプトに時間を表示するために、PS1を編集します。


~/.bashrcに下記を書き込めば良いです。

PS1="\t [\u@\h \W]\$"


ちなみに、.bashrcとか.bash_profileとか
どっちに書いたらいいんだ!問題が有ると思いますが

この場合は、bashrcです。
ためしに.bash_profile に書いてみてください。上手く行くはずです



が、例えば下記のコマンドを実行してみてください。

bash


ね、そういうことです。

おわり

awkでcutと同等のフィールド出力

なんかしらんけども、cutでなぜか上手くフィールドの指定ができないことが有る。

# こんな感じでしていするもうまくできない
grep -i ipv6 /var/log/messages | cut -d: -f 6- | sort -u
#
# [出力結果]
# wlp2s0f0: link is not ready
#


そんなときにawkだと思い通りに出力できたりする。
今回はcut -d' ' -f 6-と同等の処理をawkで実行する。

awk '{
	for (i=6;i<NF;i++) {
		printf("%s%s", $i, OFS=" ")
	}	print $NF
}' /var/log/messages


ということで、冒頭のcut部をawkに変更するとこうなる。

grep -i ipv6 /var/log/messages | awk '{for(i=6;i<NF;i++){printf("%s%s",$i,OFS=" ")}print $NF}' | sort -u
#
# [出力結果]
# IPv6: ADDRCONF(NETDEV_UP): wlp2s0f0: link is not ready
#

おわり