【fish】複数行野郎 (サンプル集)

次世代シェルである fish を使ったサンプル集です。


文字列を格納した配列変数から指定の要素を削除する
   pickup_dirlist.fish
   pickup_dirlist_2.fish
文字列のパターンマッチ判定
   match_file.fish
   match_pattern.fish
文字「:」を区切り文字として多次元配列もどきを実現する
   multi_array.fish
引数解析関数の使い方
   argparse.fish
複数プロセスの終了を待つ
   wait_jobs.fish
fzf と find を使って対話的にファイルを選択する
   zf.fish
fzf と find を使って対話的に cd を実行する
   zd.fish

 
 
 

pickup_dirlist.fish

$TARGET_DIRS にファイルシステムの先頭が格納されている。
このうち $EXCLUDE_DIRS に存在するパスを除外したうえで find を実行する。
 

#!/usr/bin/env fish
# find 実行対象外にしたいディレクトリ一覧
set -l EXCLUDE_DIRS "/lost+found" \
                    "/snap"       \
                    "/proc"       \
                    "/sbin"       \
                    "/media"      \
                    "/root"       \
                    "/opt"        \
                    "/srv"        \
                    "/cdrom"      \
                    "/lib64"      \
                    "/mnt"        \
                    "/run"        \
                    "/tmp"        \
                    "/lib"        \
                    "/dev"        \
                    ""    # End of list

# ファイルシステム(以下は例)の先頭を動的に取得する。
set -l TARGET_DIRS  ( find / -maxdepth 1 -mindepth 1 -type d )

# TARGET_DIRS から EXCLUDE_DIRS に書かれた要素を削除する
for i in ( seq 1 (count $EXCLUDE_DIRS) )
  set TARGET_DIRS ( string replace $EXCLUDE_DIRS[$i] '' $TARGET_DIRS )
end

echo $TARGET_DIRS

# 出力結果
# /var  /sys /boot   /bin   /home   /dev /usr /etc

 
 
 

pickup_dirlist_2.fish

$TARGET_DIRS にファイルシステムの先頭が格納されている。
このうち $EXCLUDE_DIRS に存在するパスを除外したうえで find を実行するが、
事前に $EXCLUDE_DIRS の各パスの末尾にある『/』を取り除いておく。
 

#!/usr/bin/env fish
# find 実行対象外にしたいディレクトリ一覧
set -l EXCLUDE_DIRS "/lost+found/" \
                    "/snap/"       \
                    "/proc/"       \
                    "/sbin/"       \
                    "/media/"      \
                    "/root/"       \
                    "/opt/"        \
                    "/srv/"        \
                    "/cdrom/"      \
                    "/lib64/"      \
                    "/mnt/"        \
                    "/run/"        \
                    "/tmp/"        \
                    "/lib/"        \
                    "/dev/"        \
                    ""    # End of list

# ファイルシステム(以下は例)の先頭を動的に取得する。
set -l TARGET_DIRS  ( find / -maxdepth 1 -mindepth 1 -type d )

# TARGET_DIRS から EXCLUDE_DIRS に書かれた要素を削除する
for i in ( seq 1 (count $EXCLUDE_DIRS) )
  set EWORD       ( echo $EXCLUDE_DIRS[$i] | sed 's:/$::g' )      # ★
  set TARGET_DIRS ( string replace -r $EWORD '' $TARGET_DIRS )
end

echo $TARGET_DIRS

# 出力結果
# /var  /sys /boot   /bin   /home   /dev /usr /etc

 
 
 

argparse.fish

引数解析関数 argparse の使い方。
参考
 fishでのオプション解析の決定版!argparseコマンドの紹介
要点
 ロングオプション名に紐付いた変数 _flag_XXXX が自動的に作成される
その他
 私の環境では、'a/add=+' xxxx yyyy zzzz などといったように複数引数を取ることができなかった.
 (一応 -a xxxx -a yyyy -a zzzz とすれば複数の引数を取ることはできた)
 

#!/usr/bin/env fish

set    FLAG_U     0
set    FLAG_N     0

set    VERSION    1.0.1


function print_version
  echo "VERSION: $VERSION"
end


function setup
    argparse -n setup -x  'u,n'            \
                          'u/update'       \
                          'n/new'          \
                          'v/version'      \
                          -- $argv ;or return 2

    if set -lq _flag_update
      set FLAG_U    1
    else if set -lq _flag_new
      set FLAG_N    1
    else if set -lq _flag_version
      print_version
      exit 0
    end
end


function start 
  # この中で FLAG_U と FLAG_N を参照して処理を分岐させる
  echo "(FLAG_U, FLAG_N) = ($FLAG_U, $FLAG_N)"
end


function main
  setup $argv
  start
end


main  $argv

 
 
 

match_file.fish

file コマンドでファイル種別を識別し、それに応じたアプリを使って操作する
なお、string コマンドは fish の組み込み関数である。
 

#!/usr/bin/env fish

if test -z {$argv[1]}
  echo '$argv[1] was empty...'  >&2
  exit 2
else if test ! -e {$argv[1]}
  echo "$argv[1]: no such file" >&2
  exit 2
end

set -l  MY_EDITOR   "nvim"
set -l  MY_OPEN     "xdg-open"
set -l  ftype ( file -b -i $argv[1] )

switch $ftype
  case "text/*"
    eval $MY_EDITOR   $argv[1]
  case "application/x-executable*charset=binary"  # binary file
    eval builtin string ' ' "fish" "-c" "$argv[1]"
  case "*x-empty*"  # empty file
    echo 'SKIP: $argv[1]: empty file...'
  case "*"
    eval $MY_OPEN $argv[1]
end 

 

match_pattern.fish

$TOGGLE_DEBUG_MODE 変数が true, TRUE, enable, ENABLE, 1 であるか判定する.
なお、string コマンドは fish の組み込み関数である。
 

#!/usr/bin/env fish

if test ( string match -r -i "\A[t|e|1]" "$TOGGLE_DEBUG_MODE" )
  echo xxxx
else 
  echo yyyy
end

 
 
 

wait_jobs.fish

my_script_1.sh、my_script_2.sh、my_script_3.sh、my_script_4.sh、
を並列で実行させて、全ての終了を待つ。
 

#!/usr/bin/env fish
for i in ( seq 1 4 )
  sh my_script_$i.sh &
end

# wait for the job to finish
while true
  set -l bg_jobs ( builtin jobs -p 2>/dev/null )
  sleep 0.5
end

echo Finished.

 
 
 
 

multi_array.fish

文字「:」を区切り文字として、多次元配列もどきを実現する。
以下は、 fishingz で用いた実装例であり、FISHINGZ_F_ACTIONS 変数には一行ずつ
「ファイル種別」「実行コマンド」「sudo使用するか」「リダイレクト設定」「バックグラウンド使用有無」
を定義している。

set -l    FISHINGZ_F_ACTIONS    '
  "text/html"       : "setsid firefox      " : ""     : "1>/dev/null 2>/dev/null" : "&"  
  "application/xml" : "setsid firefox      " : ""     : "1>/dev/null 2>/dev/null" : "&"  
  "text/html"       : "setsid firefox      " : "sudo" : "1>/dev/null 2>/dev/null" : "&"  
  "text/xml"        : "setsid firefox      " : "sudo" : "1>/dev/null 2>/dev/null" : "&"  
  "text"            : "vi"                   : "sudo" : ""                        : ""   
  "image"           : "setsid xdg-open"      : ""     : "1>/dev/null 2>/dev/null" : "&"  
  "inode/x-empty"   : "vi"                   : ""     : ""                        : ""
'

set -l  actions ( string trim $FISHINGZ_F_ACTIONS )
set -l  act_type
set -l  act_app
set -l  act_sudo
set -l  act_eoff
set -l  act_bg

for i in (seq 1 (count $actions) )
  set act_type ( string trim (echo $actions[$i] | cut -d':' -f1) | \
                 sed -e s'/"\(.*\)"/\1/g' -e s'/\'\(.*\)\'/\1/g' )
  set act_app  ( string trim (echo $actions[$i] | cut -d':' -f2) | \
                 sed -e s'/"\(.*\)"/\1/g' -e s'/\'\(.*\)\'/\1/g' )

  set act_sudo ( string trim (echo $actions[$i] | cut -d':' -f3) | \
                 sed -e s'/"\(.*\)"/\1/g' -e s'/\'\(.*\)\'/\1/g' )
  set act_eoff ( string trim (echo $actions[$i] | cut -d':' -f4) | \
                 sed -e s'/"\(.*\)"/\1/g' -e s'/\'\(.*\)\'/\1/g' )
  set act_bg   ( string trim (echo $actions[$i] | cut -d':' -f5) | \
                 sed -e s'/"\(.*\)"/\1/g' -e s'/\'\(.*\)\'/\1/g' )
  echo "$act_sudo $act_app /path/to/file $act_eoff $act_bg"
end


 

zf

fishingz の前身。
コマンドラインにて、「zf パス」として実行すると fzf によって対話的に開きたいファイルを選択する。
但し、使用するアプリは nvim 決め打ちであった。
なお、一度開いたファイルは繰り返し開く傾向にあるので、fish の HISTORY にマージするようにしている。
$HOME/.config/fish/functions/zf.fish として配置することで使用可能。

function zf
    function _add_history

        # コマンド実行履歴ファイルがあるかチェックする
        set -l hist_file /home/neko/.local/share/fish/fish_history
        if test ! -f "$hist_file"
            touch $hist_file
        end

        # 現在実行したコマンドがこれまでに実行したことがあるチェックする
        set -l pattern ( grep -w "\- cmd: $argv[1]" $hist_file )

        # 現在実行したコマンドがこれまでに存在していなければ追加する
        if test -z "$pattern"
            echo "- cmd: $argv[1]"   >> $hist_file
            echo "  when: $argv[2]"  >> $hist_file
            history merge
        end
    end

    set -l filename
    set -l timestr (date "+%s")

    if test (count $argv) -eq 0
        set filename ( find . -type f 2>/dev/null | fzf )
        if test -n "$filename"
            if test -r "$filename"
                _add_history     "sudo nvim $filename" $timestr
                sudo nvim $filename
            else
                _add_history     "nvim $filename" $timestr
                nvim $filename
            end
        end
        return 0
    else if test (count $argv) -eq 1
        if test -f $argv[1] 
            if test -r "$argv[1]"
                _add_history     "sudo nvim $argv[1]" $timestr
                sudo nvim $argv[1]
            else
                _add_history     "nvim $argv[1]" $timestr
                nvim $argv[1]
            end
        else if test -d $argv[1] 
            set filename ( find $argv[1] -type f 2>/dev/null | fzf )
            if test -n "$filename"
                if test -r "$filename"
                    _add_history     "sudo nvim $filename" $timestr
                    sudo nvim $filename
                else
                    _add_history     "nvim $filename" $timestr
                    nvim $filename
                end
            end
            return 0
        else
            echo "Error: could not found $argv[1]"
            return 1
        end
    else if test (count $argv) -gt 1
        echo "Usage: $argv[0] [directory]"
        return 1
    end
end

 

zd

fishingz の前身。
コマンドラインにて、「zd パス」として実行すると fzf によって対話的に移動したいディレクトリを選択する。
一度移動したら、履歴として保存している。

function zd
    function _add_history

        # コマンド実行履歴ファイルがあるかチェックする
        set -l hist_file /home/neko/.local/share/fish/fish_history
        if test ! -f "$hist_file"
            touch $hist_file
        end

        # 現在実行したコマンドがこれまでに実行したことがあるチェックする
        set -l pattern ( grep -w "\- cmd: $argv[1]" $hist_file )

        # 現在実行したコマンドがこれまでに存在していなければ追加する
        if test -z "$pattern"
            echo "- cmd: $argv[1]"   >> $hist_file
            echo "  when: $argv[2]"  >> $hist_file
            history merge
        end
    end

    set -l directory
    set -l timestr (date "+%s")

    if test (count $argv) -eq 0
        set directory ( find . -type d 2>/dev/null | fzf )
        if test -n "$directory" ; and test -x "$directory"
            _add_history     "cd $directory" $timestr
            cd $directory
        end
        return 0
    else if test (count $argv) -eq 1
        if test -d $argv[1] 
            set directory ( find $argv[1] -type d 2>/dev/null | fzf )
            if test -n "$directory" ; and test -x "$directory"
                _add_history     "cd $directory" $timestr
                cd $directory
            end
            return 0
        else
            echo "Error: could not found $argv[1]"
            return 1
        end
    else if test (count $argv) -gt 1
        echo "Usage: $argv[0] [directory]"
        return 1
    end
end