shellcheckはbasenameを使用しないことを推奨しています:なぜですか?


25

shellcheckを試しています

私はそのようなものを持っています

basename "${OPENSSL}" 

そして、私は次の提案を受け取ります

Use parameter expansion instead, such as ${var##*/}.

実用的な観点からは、違いは見当たりません

$ export OPENSSL=/opt/local/bin/openssl
$ basename ${OPENSSL}
openssl
$ echo ${OPENSSL##*/}
openssl

以来basenameであるPOSIX仕様、私はそれがベストプラクティスであるべき理由はない理由を行います。ヒントはありますか?


8
必要のないときに新しいプロセスをフォークします。
ヨルダン

@jordanm公平...私は効率について考えませんでした。
マッテオ

3
それはbashの以外のシェル上で動作する一方@jordanm
マッテオ

1
@JosephR。それは私が考えたものですが、それが上で動作しないことがわかりましたcshcshPOSIXではないでしょう。
テルドン

3
@terdon-cshはPOSIXから非常に遠いです。
ヨルダン

回答:


25

効率性ではなく、正確さです。basename改行を使用して、出力するファイル名を区切ります。通常、ファイル名を1つだけ渡す場合、出力に末尾の改行が追加されます。ファイル名には改行自体が含まれている場合があるため、これらのファイル名を正しく処理することは困難です。

人々が通常次のbasenameように使用するという事実により、さらに複雑になります"$(basename "$file")"。これにより、後続の改行$(command)すべてから削除するため、事態はさらに困難になりcommandます。$file改行で終わるありそうもないケースを考えてみましょう。その後basename、余分な改行を追加しますが、両方の改行"$(basename "$file")"を削除し、誤ったファイル名を残します。

もう1つの問題は、a (ダッシュ、別名)で始まるbasename場合、オプションとして解釈されることです。これは簡単に修正できます。$file-$(basename -- "$file")

堅牢な使用方法basenameは次のとおりです。

# A file with three trailing newlines.
file=$'/tmp/evil\n\n\n'

# Add an 'x' so we can tell where $file's newlines end and basename's begin.
file_x="$(basename -- "$file"; printf x)"

# Strip off two trailing characters: the 'x' added by us and the newline added by basename. 
base="${file_x%??}"

別の方法はを使用することです${file##*/}。これは簡単ですが、独自のバグがあります。特に、$fileis /またはの場合は間違っていfoo/ます。


2
非常に良いポイント、+ 1。OPが私の代わりにこれを受け入れてくれてうれしい。
テルドン

2
対位法:どのような場合が$fileありますかfoo/?もしそうなら/
ジル 'SO-悪であるのをやめる'

2
@Gilles:そのとおりです。basename結局のところ、このアプローチはハックのように優れていると考え始めています。私が思い付くことができる最高の選択肢がある${${${file}%%/#}##*/}[[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1]のzsh-特定し、ハンドルでもないどちらも、/正しく。
マット

@terdonあなたが個人的にそれを受け入れなかったことに感謝します:-)。改行を含むファイルは一般的ではありませんが、Mattにはポイントがあります。もちろん、変数置換を使用する方が効率的です。
マッテオ

16

shellcheckソースコードに関連する行は次のとおりです。

checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "dirname" =
    style id "Use parameter expansion instead, such as ${var%/*}."
checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "basename" =
    style id "Use parameter expansion instead, such as ${var##*/}."
checkNeedlessCommands _ = return ()

明示的な説明はありませんが、関数の名前(checkNeedlessCommands)に基づいて@jordanmが正しいと思われ、新しいプロセスのフォークを避けることを提案しています。


7
ソースは、あなたと一緒かもしれ:)
ジョセフ・R.

3

dirnamebasenamereadlinkなど-セキュリティが重要になってきたときに移植性の問題を作成することができます(感謝@Marcoこれが修正される)(パスのセキュリティを必要とします)。多くのシステム(Fedora Linuxなど)に配置されてい/binますが、他のシステム(Mac OSXなど)に配置されてい/usr/binます。次に、Windows上のBash、たとえばcygwin、msysなどがあります。 可能な場合は、常に純粋なB​​ashを使用することをお勧めします。 (@Marcoコメントごと)

ところで、shellcheckへのポインタのおかげで、私はそれを見たことがありません。


1
1)「セキュリティ」とはどういう意味ですか?詳しく説明してもらえますか?2)OPはまったく言及dirnameしていません。3)基本的なコアユーティリティは、どこに保存されていてもPATHにある必要があります。basenamePATHにないシステムはまだ見ていません。4)bashが利用可能であると仮定すると、移植性の問題になります。移植性が必要な場合は、常にbashから離れてPOSIXシェルを使用することをお勧めします。
マルコ

@Marcoこれらの問題を指摘してくれてありがとう。はい、ユーティリティがパス上にあることは正しいです。ただし、Bashスクリプトにセキュリティを追加する場合は、絶対パスを指定することをお勧めします。そのため、「/ bin / basename」を呼び出すとRedHatシステムで機能しますが、Macではエラーが発生します。これについては、600ページの約4分の1がこのテーマに当てられているBash Cookbookで詳しく説明されています。無料のソースであるsecure-libでセキュリティ問題に対処するための大まかな試みを行いました。
AsymLabs

@Marco Point 4は有効なコメントですが、質問(OP)は両方のsh / bashスクリプトをチェックするように設計されたshellcheckで始まり、書かれています。したがって、デフォルトでは厳密にPosixではないと想定する必要があります。
AsymLabs

パス環境変数の@Marcoセキュリティは、簡単に侵害される可能性があります。たとえば、数年前、ローカルにインストールされたRuby RVMがデフォルトでシステムパスの前にパスを置いていたことに非常に驚きました。
AsymLabs

OPはPOSIXに特に言及しており、質問にはタグが付けられてposixおり、タグは付けられていませんbash。OPにbashソリューションが必要であることを示すインジケーターが見つかりません。「純粋なBashを維持する方が常に良い」というあなたの声明は明らかに間違っています。ごめんなさい。
マルコ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.