Bash シャドウコマンド - コマンドと同じ名前の関数

Bash シャドウコマンド - コマンドと同じ名前の関数

.bashrc書き込み不可能なファイルを自動的にsudosする機能があります。

vim() {
    if [ -w "$1" ]; then
        \vim "$1"
    else
        sudo env HOME="$HOME" \vim -u ~/.vimrc "$1"
    fi
}

ファイルにsudoが必要な場合は正常に動作します。それ以外の場合は、この関数を再帰的に呼び出し、I CC まで CPU を 100% 使用します。

この回答ではいくつかのオプションがあり、すべて試してみました。実際に働くもの:

'vim' "$1" #fails
\vim "$1" #fails
command vim "$1" #Works!

他のオプションが期待どおりに機能しないのはなぜですか?

(重複していることはわかりますが、現在の質問のタイトルではSO / SEで答えを見つけるのが難しいので、私と他の人がインターネット検索で見つけることができるタイトルで質問を投稿する必要があると思いました。)

答え1

エイリアスの前に文字がある場合、エイリアスは実行されません。

alias ls='ls -la'
ls foo.txt     #will run ls -la foo.txt
\ls foo.txt    #will run ls foo.txt
'ls' foo.txt   #will run ls foo.txt
'ls foo.txt'   #will run ls foo.txt

ただし、これが関数を停止するわけではなく、command同じ名前の関数を生成する場合は、デフォルトの組み込み関数を参照する必要があります。

ls () {
    echo "not an alias"
}
alias ls='echo "an alias"'

ls foo.txt          #will echo "an alias"
\ls foo.txt         #will echo "not an alias"
'ls' foo.txt        #will echo "not an alias"
command ls foo.txt  #will actually run `ls`

説明する

問題は、最初の2つのオプションがエイリアスのみを処理できることです。同じ名前の関数やコマンドがあるかどうかを検出する特別なリダイレクト演算子ではありません。エイリアスはパイプまたはコマンドの最初の部分である場合にのみ拡張されるため、エイリアスの前に何かを入れるだけです。

alias v='sudo vim'
v x.txt
#automatically expands "v" to "sudo vim" and then does the rest of the command
# v x.txt ==> sudo vim x.txt

Bashは、知っているエイリアスのリスト(を使用して取得できます)を使用してaliasコマンドの最初の単語を拡張しようとします。エイリアスは引数を使用せず、最初の単語(スペースで区切られた)のみが可能です。vim x.txt に慣れるsudo vimim x.txt正しく拡張するには、コマンドで上記のエイリアスを使用して拡張します。

ただし、一重引用符内の内容は拡張されません。変数が表す内容の代わりにecho '$USER'リテラルが印刷されます。$USERまた、bashは\xx主に)拡張されます。これは、別名をエスケープするために特別に追加された追加のメソッドではなく、単にbash拡張が作成される方法の一部にすぎません。したがって、これらの方法を使用すると、エイリアスがコマンドを非表示にしても、実際のコマンドには引き続きアクセスできます。

答え2

他のオプションが期待どおりに機能しないのはなぜですか?

コマンドラインの実行方法に関する詳細があるためです。

基本的な概念は、コマンドラインの最初の単語がシェルが検索して実行しようとしているコマンドであることです。この場合注文する:

  1. コマンドラインは(ほとんど)メタ文字で区切られています。

    メタ文字の
    引用符がない場合に単語を区切る文字です。次のいずれか
    : &()<>スペースタブの改行

  2. 最初のラーメン引用しないword は、エイリアス定義で置き換えられるエイリアス語と一致します。ループを避けるには:一度新しい最初の単語が拡張されるエイリアスと一致する場合。これにより、ループなしで作業できます。

    $ alias ls='ls -la'
    $ ls dir               # will expand to `ls -la dir`
    

    注:エイリアス値の最後の文字が空の場合、エイリアスの後の次のコマンドワードもエイリアス拡張を確認します。

  3. (生成された)最初の単語が拡張(a $var)などの場合は置き換えられます(引用符がない場合はIFSトークン化(およびグロービング)の影響を受けます)。

    $ var='echo -e '
    $ $var test '\twords'            # will expand to `echo -e test words`
    test    words
    
  4. 上記の拡張以降(オプションの変数割り当て後)に生成された最初の単語は、次の順序で検索されます。

    • 特殊内蔵機能(POSIXモードのみ)
    • 機能
    • 組み込み機能
    • ハッシュコマンド(ハッシュに関するヘルプを読む)
    • 外部コマンド($PATHディレクトリから検索)

    見つかった最初の一致が実行されます。

テストのために、次の方法で順序を変更できます。

  • エイリアスをバイパスするには、\testまたは他の種類の拡張機能を使用してください。
  • 関数とエイリアスを無視するには、を使用しますcommand test
  • 内蔵機能を実行するにはを使用してくださいbuiltin test
  • 外部アプリケーションを実行するには、明示的なパス()を使用してください/bin/test

したがって、関数は次のように定義されます。

$ ls(){ ls; }

無限ループで呼び出されます。また、同じ最初の単語のスクリプトを呼び出します。それは次のとおりです。

#!/bin/bash
$0 "$@"

カーネルがクラッシュするまで、同じスクリプトがループ内で再度実行されます(使用中のカーネルがスクリプトの呼び出しを続けるのに制限がある場合)。

このシーケンスは以下を実行して表示されますtype -a

$ test(){ echo test function; }
$ alias test=test
$ type -a
test is aliased to `test'
test is a function
test ()
{
    echo test function
}
test is a shell builtin
test is /usr/bin/test

質問で定義された関数はエイリアスだけをバイパスします\vimが、関数はバイパスしません。エイリアスと機能をバイパスするには、コマンドを使用します。この関数は必要な操作を実行する必要があります。

vim() {
        if [ -w "$1" ]; then
            command vim "$1"
        else
            sudo HOME="$HOME" bash -c 'command vim "$@"' _ -u ~/.vimrc "$1"
        fi
      }

答え3

関数にvim呼び出しバイナリへのフルパスを指定すると、再帰ループは発生しません。また、-Hsudoオプションを使用して$ HOMEを設定します。

vim() {
    if [ -w "$1" ]; then
        command vim "$1"
    else
        sudo env HOME="$HOME" \vim -u ~/.vimrc "$1"
    fi
}

ありがとうございます。私はこれを期待していませんでしたが、今私も.bashrcそれを持っています。


編集する: 更新された呼び出しcommand vimの代わりにsudo envこれsudo -hを行うと、継続するループはありません。

関連情報