私が読んで「シェルパラメータ拡張」のGNUドキュメント次の構文が提供されます。
${parameter//pattern/string}
ファイル名拡張と同様に、パターンを拡張してパターンを生成します。範囲拡張され、最も長い一致模様 その値は次のように置き換えられます。ひも...2つのスラッシュで区切られた場合範囲そして模様...、すべてのゲーム模様に置き換えられますひも。
「すべての一致」というフレーズが与えられると、模様に置き換えられますひも「上記で複数のインスタンスが発生すると予想しました。模様次に交換ひも 一度に/g
、正規表現でグローバル検索と置換コマンドにフラグを使用する方法と同様に、単一の操作で実行されます。
remove_from_path
次のように実装されたオープンソースコード(関数)があります。
remove_from_path() {
local path_to_remove="$1"
local path_before
local result=":${PATH//\~/$HOME}:"
local counter=0
while [ "$path_before" != "$result" ]; do
counter+=1
echo "counter: $counter"
path_before="$result"
result="${result//:$path_to_remove:/:}"
done
result="${result%:}"
echo "${result#:}"
}
元のコードにはこのcounter
変数は含まれていません。ループが実行される繰り返しの回数を確認するためにこの変数を追加しましたwhile
。
ご覧のとおり、この行はresult="${result//:$path_to_remove:/:}"
GNUドキュメントで説明されているのと同じ二重スラッシュ構文を使用しています。これを考えると、すべてのインスタンスを一度に削除するwhile
必要があるため、ループを一度だけ実行したいと思います。path_to_remove
result
しかし、これは本当ではないようです。bash
shell()で次のようにversion 3.2.57
更新しました。$PATH
bash-3.2$ PATH="/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz"
その後、上記の関数をシェルにコピー/貼り付けて実行しました。私は次を見る:
bash-3.2$ remove_from_path "/foo/bar/baz"
counter: 01
counter: 011
counter: 0111
buzz
ループが3回実行されていることがわかるので、カウンターの増加が期待どおりに機能しないことを無視してくださいwhile
。${parameter//pattern/string}
二重スラッシュ構文がすべての一致を置き換える場合模様そしてひもwhile
、ループの1回の繰り返しでこれが行われないのはなぜですか?なぜ3回の繰り返しが必要なのですか?
答え1
ksh93の演算子${var//pattern/replacement}
(zsh、bash、mkshでもサポートされています)重複なしパターンの発生回数です。
${var//xxx/y}
重複する項目のうち4つを置き換えるように変更すると、非常にxxxxxx
混乱します。yy
yyyy
xxx
これは$PATH
ディレクトリのリストを表します(~
この場合、~
現在の作業ディレクトリのサブディレクトリ$HOME
に変更するとエラーが発生します)。
多くのシェル(csh、tcsh、zsh、fish、yash)はそれらを配列変数の1つにマップします。
$PATH
たとえば、zshから($path
などの配列にマップされたcsh
)ディレクトリ内のすべてのエントリを削除するには、次の手順を実行します。
path=( ${path:#$dir} )
(またはpath=( "${path[@]:#$dir}" )
空の要素を保持しますが、そこに含めたくありません$PATH
。)
bash
これは行いませんが、$PATH
Split + glob演算子を使用して配列に変換できます。
set -o noglob
IFS=:
path=( $PATH'' )
ksh93やzshなどのbashでは、${var//pattern/replacement}
構文を使用して配列内のすべての要素に適用できますが、それを行うことは"${array[@]//pattern/replacement}"
できないため役に立ちません。削除する要素を変更するだけです。
したがって、bash
要素に対してのみ繰り返すことができます。
remove_from_PATH() {
local - IFS=: dir to_remove result
set -o noglob
for dir in $PATH''; do
for to_remove do
if [[ $dir = "$to_remove" ]]; then
continue 2
fi
done
result+=( "$dir" )
done
PATH="${result[*]}"
}
(noglobなどの関数のローカルlocal -
オプションを変更するためにAlmquistシェルからコピーするには、set -o
比較的新しいバージョンのbashが必要であり、使用中と思われる古代の3.2バージョンでは機能しません。)
:
に保存されている区切りリストを変更して要素を削除するには、$PATH
各項目に次のものが必要です$to_remove
。
$to_remove:
最初に見つかった項目を空の文字列に置き換えます。:$to_remove:
途中のすべての項目(一部は:
sと重複する可能性があります)を次に置き換えます。:
:$to_remove
最後に空の文字列を削除$PATH
のみが含まれている場合、$to_remove
選択の余地はありません。空の場合は、$PATH
現在のディレクトリからコマンドを検索するのが最後なので、望むものではないからです。これはバグでよりよく処理されなければならず、上記のように実際の生活で一般的に発生しない病理学的状況では無視できます。または、検索で何も見つからなかったかどうかを/dev/null
確認できます。$PATH
だから:
remove_from_PATH() {
local to_remove dir newpath="$PATH" prev_newpath
for to_remove do
while
prev_newpath=$newpath
newpath=${newpath#"$to_remove:"}
newpath=${newpath%":$to_remove"}
newpath=${newpath//":$to_remove:"/:}
[[ $newpath != "$prev_newpath" ]]
do
continue
done
done
if [[ -n $newpath ]]; then
PATH=$newpath
else
echo >&2 'Refusing to make $PATH empty'
return 1
fi
}
答え2
問題を見つけたようですが、間違っている場合は今お知らせください。
$result
変数には次の文字列があります。
:/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz:
適用すると、result="${result//:$path_to_remove:/:}"
すべての:/foo/bar/baz:
項目が置き換えられます:
。ただし、パターンが与えられると、2番目のパスは次:
の理由で実際には一致しません。
:/foo/バー/バズ:/foo/bar/baz:/foo/バー/バズ:うなり声:
太字のパスはパターンが現れる場所です。
テストのために、次の方法を試すことができます。
result=':/foo/bar/baz1:/foo/bar/baz2:/foo/bar/baz3:buzz:'
echo "${result//:'/foo/bar/baz'?:/:}"
#Output:
:/foo/bar/baz2:buzz:
上記のように、2番目のパス(/for/bar/baz2
)は使用しているモードの影響を受けません。
したがって、パラメータ拡張のために次のことができます。
echo "${r//'/foo/bar/baz':/}" # The firsy ':' in the pattern was removed
#and instead of replace the pattern with ':' I'm replacing with nothing.
したがって、remove_from_path
関数は次のようになります。
remove_from_path() {
local path_to_remove="$1"
local path_before
local result=":${PATH//\~/$HOME}:"
local counter=0
while [ "$path_before" != "$result" ]; do
counter+=1
echo "counter: $counter"
path_before="$result"
result="${result//$path_to_remove:/}"
done
result="${result%:}"
echo "${result#:}"
}
ただし、関数のロジックに応じて、whileループは2回実行されます。これは、パラメータ拡張によって他の値を設定するpath_before
前に変数が設定されるためです。result
答え3
先行コロンが多すぎます。以下を追加しないでください。
result="/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz"
echo ${result//$path_to_remove:/:}
:::buzz
繰り返しを必要とせずにすべての項目を一度に削除することがわかります。PATH
システム変数を操作するとセッションが利用できなくなる可能性があることに注意してください。