この記事はFISH SHELL SCRIPTING MANUALから翻訳されたものであり、私の知識が浅いため、誤りや不自然な表現があるかもしれません。読者の皆様にはコメント欄でご指導いただければ幸いです。
例を通じて、fish shell スクリプトの書き方を学びます。
スクリプトの先頭にある shebang 行#
ターミナルで fish スクリプトを実行するには、次の 2 つのことを行う必要があります。
- スクリプトの先頭に次の shebang 行を追加します:
#!/usr/bin/env fish
。 - 次のコマンドを使用してファイルを実行可能としてマークします:
chmod +x <あなたの fish スクリプトファイル名>
。
変数の設定方法#
注意点として、fish では変数に代入できるすべての値は文字列であり、ブール値、整数、浮動小数点数などの概念はありません。以下は変数に値を代入する簡単な例です。これに関する詳細情報があります。
set MY_VAR "some value"
最も便利なことの一つは、シェルで実行したコマンドの出力を変数に保存することです。特定のプログラムやコマンドが他のコマンドを実行する必要がある値を返すかどうかをテストする際(文字列比較、if 文、switch 文を使用)に役立ちます。以下はそのようにするいくつかの例です。
set CONFIG_FILE_DIFF_OUTPUT (diff ~/Downloads/config.fish ~/.config/fish/config.fish)
set GIT_STATUS_OUTPUT (git status --porcelain)
変数のスコープ: local, global, global-export#
時には、変数を子プロセスにエクスポートする必要があり、時には変数をグローバルスコープにエクスポートする必要があります。また、関数のローカルスコープに変数を制限したい場合もあります。fish ドキュメントの set
関数のセクションに関する詳細情報があります。
- 変数を関数のローカルスコープに制限するには(同名のグローバル変数があっても)
set -l
を使用します。このタイプの変数は、全体の fish shell では使用できません。例として、関数の範囲内で特定の値を保存するためのローカル変数が挙げられます。例えば、set -l fname (realpath)
。 set -x
を使用して変数をエクスポートします(現在の fish shell でのみ使用可能)。例えば、crontab
のヘッドレス環境で実行される fish 関数内で X11 セッションのためにDISPLAY
環境変数を設定します。set -gx
を使用して変数をグローバルにエクスポートします(現在実行中の fish shell プロセスだけでなく、オペレーティングシステムの任意のプログラムで使用可能)。例えば、ローカルで実行されているすべてのプログラムのためにJAVA_HOME
環境変数を設定します。
リスト#
以下は、変数に値を追加する例です。デフォルトでは、fish 変数はリストです。
set MY_VAR $MY_VAR "another value"
リストを作成する方法は以下の通りです。
set MY_LIST "value1" "value2" "value3"
コマンドの戻り値を保存する#
以下は、コマンドの実行結果を変数に保存する例です。
set OUR_VAR (math 1+2)
set OUR_VAR (date +%s)
set OUR_VAR (math $OUR_VAR / 60)
すべての fish 変数がリストであるため、[n]
演算子を使用して単一の要素にアクセスできます。ここで n=1
は最初の要素を示します(0 ではありません)。以下はその例です。負の数は末尾から要素にアクセスすることを示します。
set LIST one two three
echo $LIST[1] # one
echo $LIST[2] # two
echo $LIST[3] # three
echo $LIST[-1] # 上の行と同じ
範囲#
変数 / リストの範囲を使用できます。上記の例を続けます。
set LIST one two three
echo $LIST[1..2] # one two
echo $LIST[2..3] # two three
echo $LIST[-1..2] # three two
for ループの書き方#
変数がデフォルトでリストを含むため、簡単にそれらをループ処理できます。以下はその例です。
set FOLDERS bin
set FOLDERS $FOLDERS .atom
set FOLDERS $FOLDERS "my foldername"
for FOLDER in $FOLDERS
echo "item: $FOLDER"
end
上記のコードを簡略化するために、set
コマンドを同じ行に置くこともできます。以下のように:
set FOLDERS bin .atom "my foldername"
for FOLDER in $FOLDERS
echo "item: $FOLDER"
end
for 文全体を単一行にすることもできます。以下のように:
set FOLDERS bin .atom "my foldername"
for FOLDER in $FOLDERS ; echo "item: $FOLDER" ; end
if 文の書き方#
if 文を書く際の鍵は、test
コマンドを使用して特定の式のブール値を評価することです。文字列比較やファイルやフォルダの存在をテストすることができます。以下はいくつかの例です。逆条件をチェックするために、not
演算子を test
の前に使用することもできます。
よく使う条件#
配列のサイズをチェックします。$argv
には、コマンドラインからスクリプトに渡された引数のリストが含まれています。
if test (count $argv) -lt 2
echo "Usage: my-script <arg1> <arg2>"
echo "Eg: <arg1> can be 'foo', <arg2> can be 'bar'"
else
echo "👋 Do something with $arg1 $arg2"
end
変数の文字列比較。
if test $hostname = "mymachine"
echo "hostname is mymachine"
end
ファイルの存在をチェック:
if test -e "somefile"
echo "somefile exists"
end
フォルダの存在をチェック:
if test -d "somefolder"
echo "somefolder exists"
end
ファイルのワイルドカードの存在をチェックすることは、ファイルやフォルダのチェックとは少し異なります。これは、fish がワイルドカードを処理する方法によるものです。fish は、コマンドを実行する前にまずそれらを展開します。
set -l files ~/Downloads/*.mp4 # このワイルドカード式は展開され、実際のファイルを含みます
if test (count $files) -gt 0
mv ~/Downloads/*.mp4 ~/Videos/
echo "📹 Moved '$files' to ~/Videos/"
else
echo "⛔ No mp4 files found in Downloads"
end
上記の例で not
演算子を使用した例:
if not test -d "somefolder"
echo "somefolder does not exist"
end
プログラム、スクリプト、または関数の終了コード#
終了コードを使用する考え方は、あなたの関数や全体の fish スクリプトが、終了コードを理解できる他のプログラムによって使用される可能性があるということです。言い換えれば、if 文が終了コードを使用して特定の条件を判断することがあるということです。これは、他のコマンドラインプログラムと一緒に使用される非常に一般的なパターンです。終了コードは、関数の戻り値とは異なります。
以下は、git
コマンドの終了コードを使用した例です:
if (git pull -f --rebase)
echo "git pull with rebase worked without any issues"
else
echo "Something went wrong that requires manual intervention, like a merge conflict"
end
コマンドの実行がエラーなしで行われたかどうかをテストする例:
if sudo umount /media/user/mountpoint
echo "Successfully unmounted /media/user/mountpoint"
end
また、$status
変数の値をチェックすることもできます。fish はコマンドを実行した後、その戻り値をこの変数に保存します。これに関する詳細情報があります。
関数を書くときは、関数やループを終了するために次のキーワードを使用できます:return
。return
の後には数字が続くことがあり、その意味は次の通りです:
return
またはreturn 0
- 関数が正常に終了したことを示します。return 1
または他の 0 より大きい数字 - 関数に問題が発生したことを示します。
fish shell 自体を終了するには exit
を使用します。整数の終了コードの意味は上記と同じです。
set -q と test -z の違い#
if 文で set -q
と test -z
を使用して変数が空かどうかをチェックする際には、いくつかの微妙な違いがあります。
test -z
を使用する際は、変数を引用符で囲むことを確認してください。変数が引用符で囲まれていない場合、そのコマンドは特定のエッジケースでエラーを引き起こす可能性があります。- ただし、
set -q
を使用して変数が設定されているかどうかをテストすることができ、引用符で囲む必要はありません。
以下はその例です:
set GIT_STATUS (git status --porcelain)
if set -q $GIT_STATUS ; echo "No changes in repo" ; end
if test -z "$GIT_STATUS" ; echo "No changes in repo" ; end
and、or 演算子を使用した複数条件の判断#
複数の条件を単一の文に組み合わせたい場合、or
と and
演算子を使用できます。条件の逆をチェックしたい場合は、!
を使用できます。以下は、コマンドラインで渡された 2 つの引数をチェックする関数の例です。これは、私たちが説明したロジックです:
- 両方の引数が欠落している場合、コマンドラインにヘルプ情報を印刷し、早期に
return
します。 - どちらか一方の引数が欠落している場合は、どちらか一方の引数が欠落していることを示すメッセージを表示し、早期に
return
します。
function requires-two-arguments
# 引数が渡されていない
if set -q "$argv"
echo "Usage: requires-two-arguments arg1 arg2"
return 1
end
# 引数が1つだけ渡されている
if test -z "$argv[1]"; or test -z "$argv[2]"
echo "arg1 or arg2 can not be empty"
return 1
end
echo "Thank you, got 1) $argv[1] and 2) $argv[2]"
end
以下はコードの注釈です:
-
set -q $variable
関数は何をしますか?$variable
が空の場合、true を返します。 -
set -q
を使用して変数が存在するかどうかを判断するためにtest
関数を置き換えたい場合は、次のように使用できます:if test -z "$variable"
if test ! -n "$variable"
またはif not test -n "$variable"
-
上記の
or
チェックをtest
に置き換えたい場合、次のようになります:if test -z "$argv[1]"; or test -z "$argv[2]"
。译者注:上記のコードはすでに
test -z
であり、この文は少し冗長です。おそらく、元のコードは
set -q "$argv[1]"; or set -q "$argv[2]"
だったと思われます。 -
or
やand
演算子を使用する際は、条件式を終了するために;
を使用する必要があります。 -
変数を空の引用符で囲むことを確認してください。変数に空の文字列が含まれている場合、これらの引用符がないと文がエラーを引き起こすことになります。
以下は、$variable
が空かどうかをテストする別の例です:
if test -z "$variable" ; echo "empty" ; else ; echo "non-empty" ; end
以下は、$variable
が文字列を含んでいるかどうかをテストする別の例です:
if test -n "$variable" ; echo "non-empty" ; else ; echo "empty" ; end
もう一つの一般的な演算子: not#
以下は、not
演算子を使用して文字列が文字列の部分を含んでいるかどうかをテストする例です:
if not string match -q "*md" $argv[1]
echo "The argument passed does not end in md"
else
echo "The argument passed ends in md"
end
参考資料#
- test コマンド
- set コマンド
- if コマンド
- stackoverflow の回答: fish 変数が空かどうかを確認する方法
- stackoverflow の回答: fish if 文で複数の条件を指定する方法
区切り文字を使用して文字列を分割する方法#
特定の状況では、コマンドの出力(文字列)を取得し、特定の区切り文字で分割して出力文字列の一部だけを使用したい場合があります。例えば、特定のファイルの SHA チェックサムを取得する場合です。コマンド shasum <filename>
は、df..d8 <filename>
のような出力を生成します。SHA の部分だけを取得したいと仮定し、区切り文字が 2 つの空白文字であることがわかっている場合、次のようにしてチェックサムの部分を取得し、$checksum
に保存できます。string split
コマンドに関する詳細情報があります。
set CHECKSUM_ARRAY_STRING (shasum $FILENAME)
set CHECKSUM_ARRAY (string split " " $SOURCE_CHECKSUM_ARRAY)
set CHECKSUM $CHECKSUM_ARRAY[1]
文字列比較を実行する方法#
文字列内の部分文字列の一致をテストするには、string match
コマンドを使用します。このコマンドに関する詳細情報は以下の通りです:
以下は実際のアプリケーションの例です。注意点として、-q
または --quiet
を使用する場合、条件が満たされると(成功すると)文字列の出力は表示されません。
if string match -q "*myname*" $hostname
echo "$hostname contains myname"
else
echo "$hostname does not contain myname"
end
以下は文字列の正確な一致をテストする例です:
if test $hostname = "machine-name"
echo "Exact match"
else
echo "Not exact match"
end
文字列が空かどうかをテストする例:
if set -q $my_variable
echo "my_variable is empty"
end
以下は、ruby-dev
と ruby-bundler
パッケージがインストールされているかどうかをテストする複雑な例です。インストールされている場合は jekyll
を実行し、インストールされていない場合はこれらのパッケージをインストールします。
# $packageName がインストールされている場合は "true" を返し、そうでない場合は "false" を返します。
# これを if 文で次のように使用します:
#
# if string match -q "false" (isPackageInstalled my-package-name)
# echo "my-package-name is not installed"
# else
# echo "my-package-name is installed"
# end
function isPackageInstalled -a packageName
set packageIsInstalled (dpkg -l "$packageName")
if test -z "$packageIsInstalled"
set packageIsInstalled false
else
set packageIsInstalled true
end
echo $packageIsInstalled
end
# パッケージがインストールされているかどうかを確認するための詳細情報: https://askubuntu.com/a/823630/872482
if test (uname) = "Linux"
echo "🐒isPackageInstalled does-not-exist:" (isPackageInstalled does-not-exist)
if string match -q "false" (isPackageInstalled ruby-dev) ;
or string match -q "false" (isPackageInstalled ruby-bundler)
# ruby をインストールします
echo "ruby-bundler または ruby-dev がインストールされていません; 今すぐインストールします..."
echo sudo apt install -y ruby-bundler ruby-dev
else
bundle install
bundle update
bundle exec jekyll serve
end
end
文字列の switch 文を書く方法#
文字列の switch 文を作成するには、ここでも test
コマンドを使用します(if 文と同様です)。case
文は部分文字列に一致する必要があり、ワイルドカードと一致させたい部分文字列の組み合わせを使用して部分文字列を表現できます。以下はその例です。
switch $hostname
case "*substring1*"
echo "Matches $hostname containing substring1"
case "*substring2*"
echo "Matches $hostname containing substring2"
end
これらを if 文と組み合わせて、最終的に次のように見えることもできます:
if test (uname) = "Darwin"
echo "Machine is running macOS"
switch $hostname
case "*MacBook-Pro*"
echo "hostname has MacBook-Pro in it"
case "*MacBook-Air*"
echo "hostname has MacBook-Air in it"
end
else
echo "Machine is not running macOS"
end
文字列を実行する方法#
スクリプト内で生成された文字列を実行する最も安全な方法は、次のパターンを使用することです。
echo "ls \
-la" | sh
これにより、デバッグが容易になり、\
を使用して複数行に分割する際に奇妙なエラーを回避できます。
関数を書く方法#
fish 関数は、オプションで引数を受け取るコマンドのリストです。これらの引数はリストとして渡されます(fish のすべての変数はリストです)。
以下はその例です:
function say_hi
echo "Hi $argv"
end
say_hi
say_hi everbody!
say_hi you and you and you
関数を書いた後、type
を使用してそれが何であるかを確認できます。例えば:type say_hi
は、あなたが作成した関数を表示します。
関数に引数を渡す方法#
$argv
を使用して関数に渡された引数を調べるだけでなく、関数が期待する具名引数のリストを提供することもできます。これに関する公式ドキュメントがあります。
覚えておくべき重要な点:
- 引数名には
-
文字を含めることはできず、_
を代わりに使用します。 - 引数を関数に渡す際に
(
と)
を使用しないでください。単にスペースで区切った単一行で引数を渡すだけで済みます。
以下はその例です:
function testFunction -a param1 param2
echo "arg1 = $param1"
echo "arg2 = $param2"
end
testFunction A B
以下は、関数に渡された引数が存在するかどうかをテストする別の例です:
# 引数名にはダッシュを含めることができないことに注意してください。アンダースコアのみ使用できます。
function my-function -a extension search_term
if test (count $argv) -lt 2
echo "Usage: my-function <extension> <search_term>"
echo "Eg: <extension> can be 'fish', <search_term> can be 'test'"
else
echo "✋ Do something with $extension $search_term"
end
end
関数からの戻り値#
関数から戻り値(通常は単なる文字列)を返す必要がある場合もあります。複数の改行で区切られた文字列を返すこともできます。いずれにせよ、これを実現するメカニズムは同じです。echo
を使用して戻り値を stdout に出力するだけです。
以下はその例です:
function getSHAForFilePath -a filepath
set NULL_VALUE ""
# $filepath が提供されていない、または $filepath が存在しない場合 -> $NULL_VALUE で早期リターン。
if set -q $filepath; or not test -e $filepath
echo $NULL_VALUE
return 0
else
set SHASUM_ARRAY_STRING (shasum $filepath)
set SHASUM_ARRAY (string split " " $SHASUM_ARRAY_STRING)
echo $SHASUM_ARRAY[1]
end
end
function testTheFunction
echo (getSHAForFilePath ~/local-backup-restore/does-not-exist.fish)
echo (getSHAForFilePath)
set mySha (getSHAForFilePath ~/local-backup-restore/test.fish)
echo $mySha
end
testTheFunction
依存関係のファイルとフォルダのパスを処理する方法#
スクリプトが複雑になるにつれて、複数のスクリプトを読み込む問題に対処する必要があるかもしれません。この場合、source my-script.sh
を使用して現在のスクリプトから他のスクリプトをインポートできます。ただし、fish は現在のディレクトリ、つまりスクリプトを実行し始めたディレクトリで my-script.sh
ファイルを探しますが、そのディレクトリはこの依存関係を読み込む必要がある場所と一致しない可能性があります。主スクリプトが $PATH
にあり、依存関係がそこにない場合にこの問題が発生します。この場合、主スクリプト内で次の操作を実行できます:
set MY_FOLDER_PATH (dirname (status --current-filename))
source $MY_FOLDER_PATH/my-script.fish
このコードは、主スクリプトが実行されているフォルダを取得し、それを MY_FOLDER_PATH
に保存し、その後 source
コマンドを使用して任意の依存関係を読み込むことができるようにします。この方法には制限があります。MY_FOLDER_PATH
に保存されるのは主スクリプトの実行位置に対する相対パスです。これは、絶対パス名が必要な場合を除いて、あまり気にしない微妙な詳細です。この場合、次の操作を実行できます:
set MY_FOLDER_PATH (realpath (dirname (status --current-filename)))
source $MY_FOLDER_PATH/my-script.fish
realpath
を使用すると、フォルダの絶対パスを提供できるため、必要な場合に使用できます。
複数行の文字列をファイルに書き込む方法#
多くの場合、スクリプト内で文字列や複数行の文字列を新しいファイルや既存のファイルに書き込む必要があります。
以下は、ファイルに単一行の文字列を書き込む例です:
# echo "echo 'ClientAliveInterval 60' >> recurring-tasks.log" | xargs -I% sudo sh -c %
set linesToAdd "TCPKeepAlive yes" "ClientAliveInterval 60" "ClientAliveCountMax 120"
for line in $linesToAdd
set command "echo '$line' >> /etc/ssh/sshd_config"
executeString "$command | xargs -I% sudo sh -c %"
end
以下は、ファイルに複数行の文字列を書き込む例です:
# 複数行の文字列を書き込む方法に関する詳細情報: https://stackoverflow.com/a/35628657/2085356
function _workflowWriteEmptyMarkdownContentToFile --argument datestr filename
echo > $filename "\
---
Title: About $filename
Date: $datestr
---
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Your heading
"
end
カラー付きの echo 出力を作成する方法#
set_color
関数を使用すると、fish は echo
を使用して stdout
に印刷されるテキストの内容に色を付けたり、フォーマットしたりできます。これは、異なる前景、背景色、太字、斜体、または下線の出力が必要なテキスト出力を作成する際に非常に便利です。このコマンドを使用する方法はたくさんありますが、以下は 2 つの例です(echo
文内でインライン、または単独で使用):
function myFunction
if test (count $argv) -lt 2
set -l currentFunctionName (status function)
echo "Usage: "(set_color -o -u)"$currentFunctionName"(set_color normal)\
(set_color blue)" <arg1> "\
(set_color yellow)"<arg2>"(set_color normal)
set_color blue
echo "- <arg1>: Something about arg1."
set_color yellow
echo "- <arg2>: Something about arg2"
set_color normal
return 1
end
end
注意:
- 前の文で設定されたフォーマットオプションをリセットするために
set_color normal
を呼び出す必要があります。 set_color -u
は下線を示し、set_color -o
は太字を示します。
ユーザー入力を取得する方法#
特定の状況では、破壊的な操作を実行する前にユーザーに確認を求めたり、関数の特定の引数(コマンドラインから渡されていない引数)をユーザーに入力してもらう必要がある場合があります。このような場合、read
関数を使用して stdin
からユーザー入力を取得できます。
以下の関数は、'Y'/'y' の場合に 0 を返し、'N'/'n' の場合に 1 を返します。
# fish read 関数を使用してユーザーに確認を求める方法に関する詳細情報: https://stackoverflow.com/a/16673745/2085356
# fish `read` 関数に関する詳細情報: https://fishshell.com/docs/current/cmds/read.html
function _promptUserForConfirmation -a message
if not test -z "$message"
echo (set_color brmagenta)"🤔 $message?"
end
while true
# read -l -P '🔴 Do you want to continue? [y/N] ' confirm
read -l -p "set_color brcyan; echo '🔴 Do you want to continue? [y/N] ' ; set_color normal; echo '> '" confirm
switch $confirm
case Y y
return 0
case '' N n
return 1
end
end
end
以下は、_promptUserForConfirmation
関数を使用した例です:
if _promptUserForConfirmation "Delete branch $featureBranchName"
git branch -D $featureBranchName
echo "👍 Successfully deleted $featureBranchName"
else
echo "⛔ Did not delete $featureBranchName"
end
sed を使用する方法#
これは、不要なファイルの部分を削除するのに非常に便利です。特に、xargs
を使用して find
の結果をパイプ処理する際に便利です。
以下は、各ファイルの先頭から './' を削除する例です:
echo "./.Android" | sed 's/.\///g'
sed
、find
、および xargs
を一緒に使用するより複雑な例:
set folder .Android*
find ~ -maxdepth 1 -name $folder | sed 's/.\///g' | \
xargs -I % echo "cleaned up name: %"
xargs を使用する方法#
これは、特定のコマンドの出力を他のコマンドの引数として使用するのに便利です。
以下は簡単な例です:ls | xargs echo "folders: "
。
- 生成される出力は:
folders: idea-http-proxy-settings images tmp
- 出力内で引数がどのように接続されているかに注意してください。
以下は、-I %
を使用して引数を任意の場所に配置できる少し異なる例です(末尾だけでなく)。
ls | xargs -I % echo "folder: %"
生成される出力:
folder: idea-http-proxy-settings
folder: images
folder: tmp
注意:各引数は別々の行に表示されます。
cut を使用して文字列を切り分ける方法#
例えば、文字列 "token1:token2"
があり、この文字列を切り分けて最初の部分だけを保持したい場合、次の cut
コマンドを使用できます。
echo "token1:token2" | cut -d ':' -f 1
-d ':'
-:
区切り文字で文字列を分割します-f 1
- トークン化された文字列の最初のフィールドを保持します
以下は、~/github/developerlife.com
内のすべての HTML ファイルを検索し、fonts.googleapis
を含むファイルを見つけて subl
で開く例です:
cd ~/github/developerlife.com
echo \
"find . -name '*html' | \
xargs grep fonts.googleapis | \
cut -d ':' -f 1 | \
xargs subl" \
| sh
スクリプトの実行にかかる時間を計算する方法#
function timed -d 実行したいプログラムや関数を引数として渡します
set START_TS (date +%s)
# ここにあなたのコードが入ります。
$argv
sleep 5
set END_TS (date +%s)
set RUNTIME (math $END_TS - $START_TS)
set RUNTIME (math $RUNTIME / 60)
echo "⏲ Total runtime: $RUNTIME min ⏲"
end