シェル スクリプトで $PATH 要素を操作するにはどうすればよいですか?
-
07-07-2019 - |
質問
PATH のようなシェル変数から要素を削除する慣用的な方法はありますか?
それは私が撮りたいものです
PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.
そして 取り除く または 交換する の /path/to/app/bin
変数の残りの部分を破壊することなく。許可してもらった追加ポイント 置く 任意の位置に新しい要素を追加します。ターゲットは明確に定義された文字列によって認識可能であり、リスト内のどの時点でも出現する可能性があります。
私はこれが行われたのを見てきたので、おそらく自分で何かを組み立てることができると思いますが、良いアプローチを探しています。移植性と標準化がプラスになります。
私は bash を使用していますが、お気に入りのシェルでの例も歓迎します。
ここでのコンテキストは、数十の実行可能ファイルを生成し、ファイルシステムにデータを隠し、環境変数を使用する大規模な科学分析パッケージの複数のバージョン (1 つは分析を実行するため、もう 1 つはフレームワークで作業するため) を簡単に切り替える必要があるということです。これらすべてを見つけるのに役立ちます。バージョンを選択するスクリプトを作成したいのですが、 $PATH
現在アクティブなバージョンに関連する要素を削除し、新しいバージョンに関連する同じ要素に置き換えます。
これは再発防止の問題に関係します。 $PATH
ログインスクリプトなどを再実行するときの要素。
- 以前の同様の質問: cshでパス変数が重複しないようにする方法
- その後の同様の質問: Bash で $PATH 変数からパスを削除する最もエレガントな方法は何ですか?
解決
dmckeeから提案されたソリューションへの対応:
- 一部のバージョンのBashでは関数名にハイフンを使用できますが、他のバージョン(MacOS X)では使用できません。
- 関数の終了直前にreturnを使用する必要はありません。
- すべてのセミコロンが必要というわけではありません。
- path-element-by-patternで値をエクスポートする理由がわかりません。
export
は、グローバル変数の設定(または作成)に相当するものと考えてください。可能な限り避けてください。 - 「
replace-path PATH $ PATH / usr
」が何を期待しているのかわかりませんが、期待どおりには動作しません。
次を含むPATH値を検討してください:
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
( ' replace-path PATH $ PATH / usr
'から)取得した結果は次のとおりです。
.
/Users/jleffler/bin
/local/postgresql/bin
/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/local/bin
/bin
/bin
/sw/bin
/sbin
/sbin
/ usrは(完全な)パス要素として表示されず、パス要素の一部としてのみ表示されるため、元のパスを取得することを期待していました。
これは、 sed
コマンドのいずれかを変更することにより、 replace-path
で修正できます。
export $path=$(echo -n $list | tr ":" "\n" | sed "s:^$removestr\$:$replacestr:" |
tr "\n" ":" | sed "s|::|:|g")
「|」の代わりに「:」を使用しました「|」以降の代替部分を分離するPATHの定義により、コロンはできませんが、(理論上)パスコンポーネントに表示できます。 2番目の sed
により、PATHの途中から現在のディレクトリが削除される可能性があります。つまり、PATHの正当な(逆方向の)値は次のようになります。
PATH=/bin::/usr/local/bin
処理後、現在のディレクトリはPATH上になくなります。
一致を固定する同様の変更は、 path-element-by-pattern
で適切です:
export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 "^$pat\# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace_path PATH $PATH /exact/path/to/remove
# replace_path_pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
# replace_path PATH $PATH /exact/path/to/remove /replacement/path
# replace_path_pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a ":" delimited list to work from (e.g. $PATH)
# $3 the precise string to be removed/replaced
# $4 the replacement string (use "" for removal)
function replace_path () {
path=$1
list=$2
remove=$3
replace=$4 # Allowed to be empty or unset
export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
tr "\n" ":" | sed 's|:$||')
}
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a ":" delimited list to work from (e.g. $PATH)
# $3 a grep pattern identifying the element to be removed/replaced
# $4 the replacement string (use "" for removal)
function replace_path_pattern () {
path=$1
list=$2
removepat=$3
replacestr=$4 # Allowed to be empty or unset
removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\#!/usr/bin/perl -w
#
# "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp echo
xpath=$PATH
replace_path xpath $xpath /usr
echopath $xpath
echo
xpath=$PATH
replace_path_pattern xpath $xpath /usr/bin /work/bin
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin
echopath xpath
quot;
#
# Print the components of a PATH variable one per line.
# If there are no colons in the arguments, assume that they are
# the names of environment variables.
@ARGV = $ENV{PATH} unless @ARGV;
foreach $arg (@ARGV)
{
$var = $arg;
$var = $ENV{$arg} if $arg =~ /^[A-Za-z_][A-Za-z_0-9]*$/;
$var = $arg unless $var;
@lst = split /:/, $var;
foreach $val (@lst)
{
print "$val\n";
}
}
quot;)
replace_path "$path" "$list" "$removestr" "$replacestr"
}
quot;)
grep -m 1
は標準ではありません(これはGNU拡張であり、MacOS Xでも利用可能です)。実際、 echo
の -n
オプションも非標準です。改行をエコーからコロンに変換するために追加された末尾のコロンを削除する方が良いでしょう。パターンごとのパス要素は一度だけ使用され、望ましくない副作用があるため( $ removestr
と呼ばれる既存のエクスポートされた変数を覆い隠します)、本体で適切に置き換えることができます。これは、スペースや不要なファイル名の展開の問題を回避するための引用符のより寛大な使用とともに、次のことにつながります。
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/work/bin
/bin
/sw/bin
/usr/sbin
/sbin
.
/Users/jleffler/bin
/work/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin
echopath
というPerlスクリプトがあり、PATHに似た変数の問題をデバッグするときに便利です。
replace_path PATH /usr/bin /work/bin
下のテストコードで変更したソリューションを実行すると:
list=$(eval echo '
出力は次のとおりです。
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace_path PATH /exact/path/to/remove
# replace_path_pattern PATH <grep pattern for target path>
#
# To replace a path:
# replace_path PATH /exact/path/to/remove /replacement/path
# replace_path_pattern PATH <target pattern> /replacement/path
#
###############################################################################
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 the precise string to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace_path () {
path=$1
list=$(eval echo '
これは私には正しいように見えます-少なくとも、問題の定義に関しては。
echopath LD_LIBRARY_PATH
は $ LD_LIBRARY_PATH
を評価することに注意してください。関数がそれを実行できればいいので、ユーザーは次のように入力できます。
echo
xpath=$PATH
replace_path xpath /usr
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath /usr/bin /work/bin
echopath xpath
echo
xpath=$PATH
replace_path_pattern xpath "/usr/.*/bin" /work/bin
echopath xpath
それは以下を使用して行うことができます:
<*>
これにより、このコードの改訂が行われます。
<*>
次の改訂テストも機能するようになりました。
<*>
以前と同じ出力が生成されます。
$path)
出力は次のとおりです。
<*>
これは私には正しいように見えます-少なくとも、問題の定義に関しては。
echopath LD_LIBRARY_PATH
は $ LD_LIBRARY_PATH
を評価することに注意してください。関数がそれを実行できればいいので、ユーザーは次のように入力できます。
<*>
それは以下を使用して行うことができます:
<*>
これにより、このコードの改訂が行われます。
<*>
次の改訂テストも機能するようになりました。
<*>
以前と同じ出力が生成されます。
$path)
remove=$2
replace=$3 # Allowed to be empty or unset
export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
tr "\n" ":" | sed 's|:$||')
}
# Remove or replace an element of $1
#
# $1 name of the shell variable to set (e.g. PATH)
# $2 a grep pattern identifying the element to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace_path_pattern () {
path=$1
list=$(eval echo '
これは私には正しいように見えます-少なくとも、問題の定義に関しては。
echopath LD_LIBRARY_PATH
は $ LD_LIBRARY_PATH
を評価することに注意してください。関数がそれを実行できればいいので、ユーザーは次のように入力できます。
<*>
それは以下を使用して行うことができます:
<*>
これにより、このコードの改訂が行われます。
<*>
次の改訂テストも機能するようになりました。
<*>
以前と同じ出力が生成されます。
$path)
出力は次のとおりです。
<*>これは私には正しいように見えます-少なくとも、問題の定義に関しては。
echopath LD_LIBRARY_PATH
は $ LD_LIBRARY_PATH
を評価することに注意してください。関数がそれを実行できればいいので、ユーザーは次のように入力できます。
それは以下を使用して行うことができます:
<*>これにより、このコードの改訂が行われます。
<*>次の改訂テストも機能するようになりました。
<*>以前と同じ出力が生成されます。
$path) removepat=$2 replacestr=$3 # Allowed to be empty or unset removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\<*>quot;) replace_path "$path" "$removestr" "$replacestr" }これは私には正しいように見えます-少なくとも、問題の定義に関しては。
echopath LD_LIBRARY_PATH
は $ LD_LIBRARY_PATH
を評価することに注意してください。関数がそれを実行できればいいので、ユーザーは次のように入力できます。
それは以下を使用して行うことができます:
<*>これにより、このコードの改訂が行われます。
<*>次の改訂テストも機能するようになりました。
<*>以前と同じ出力が生成されます。
$path)出力は次のとおりです。
<*>これは私には正しいように見えます-少なくとも、問題の定義に関しては。
echopath LD_LIBRARY_PATH
は $ LD_LIBRARY_PATH
を評価することに注意してください。関数がそれを実行できればいいので、ユーザーは次のように入力できます。
それは以下を使用して行うことができます:
<*>これにより、このコードの改訂が行われます。
<*>次の改訂テストも機能するようになりました。
<*>以前と同じ出力が生成されます。
他のヒント
Bashの$ PATH変数からパスを削除する最もエレガントな方法は何ですか?:
#!/bin/bash
IFS=:
# convert it to an array
t=($PATH)
unset IFS
# perform any array operations to remove elements from the array
t=(${t[@]%%*usr*})
IFS=:
# output the new array
echo "${t[*]}"
またはワンライナー:
PATH=$(IFS=':';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS=':';echo "${t[*]}");
要素を削除するには、sedを使用できます:
#!/bin/bash
NEW_PATH=$(echo -n $PATH | tr ":" "\n" | sed "/foo/d" | tr "\n" ":")
export PATH=$NEW_PATH
&quot; foo&quot;を含むパスを削除しますパスから。
sedを使用して、特定の行の前後に新しい行を挿入することもできます。
編集:sortおよびuniqをパイプ処理することで重複を削除できます:
echo -n $PATH | tr ":" "\n" | sort | uniq -c | sed -n "/ 1 / s/.*1 \(.*\)/\1/p" | sed "/foo/d" | tr "\n" ":"
&quot; cshでパス変数が重複しないようにする方法&quot;。繰り返される要素が存在しないようにすることに重点を置いていますが、私が提供するスクリプトは次のように使用できます。
export PATH=$(clnpath $head_dirs:$PATH:$tail_dirs $remove_dirs)
$ head_dirsに1つ以上のディレクトリがあり、$ tail_dirsに1つ以上のディレクトリがあり、$ remove_dirsに1つ以上のディレクトリがあると仮定すると、シェルを使用して、先頭、現在、および末尾の部分を巨大な値に連結し、次に、$ remove_dirsにリストされている各ディレクトリを結果から削除し(存在しない場合はエラーではありません)、パス内のディレクトリの2回目以降の出現を削除します。
これは、パスコンポーネントを特定の位置(先頭または末尾以外、間接的にのみ)に配置することには対応していません。表記上、新しい要素を追加する場所、または置き換える要素を指定するのは面倒です。
bash自体が検索と置換を実行できることに注意してください。通常の「1回またはすべて」のケースで、[in]センシティブなオプションを期待できます。
manページから:
$ {parameter / pattern / string}
パターンは、パス名の展開と同様に展開されてパターンが生成されます。パラメーターが展開され、その値に対するパターンの最長一致がストリングに置き換えられます。 Ipatternが/で始まる場合、patternのすべての一致はstringに置き換えられます。通常、最初の一致のみが置き換えられます。パターンが#で始まる場合、パラメーターの展開された値の先頭で一致する必要があります。パターンが%で始まる場合、パラメーターの展開値の最後で一致する必要があります。文字列がnullの場合、パターンの一致は削除され、/次のパターンは省略できます。パラメーターが@または *、置換操作は各位置パラメーターに順番に適用され、展開は結果のリストです。パラメーターが@で添え字付けされた配列変数である場合 *、置換操作は配列の各メンバーに順番に適用され、展開は結果のリストになります。
また、$ IFS(入力フィールド区切り文字)を目的の区切り文字に設定することにより、フィールド分割を行うことができます。
OK、すべてのレスポンダーに感謝します。フロリンの答えをカプセル化したバージョンを用意しました。最初のパスは次のようになります。
# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
# replace-path PATH $PATH /exact/path/to/remove
# replace-path-pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
# replace-path PATH $PATH /exact/path/to/remove /replacement/path
# replace-path-pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################
# Finds the _first_ list element matching $2
#
# $1 name of a shell variable to be set
# $2 name of a variable with a path-like structure
# $3 a grep pattern to match the desired element of $1
function path-element-by-pattern (){
target=$1;
list=$2;
pat=$3;
export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 $pat);
return
}
# Removes or replaces an element of $1
#
# $1 name of the shell variable to set (i.e. PATH)
# $2 a ":" delimited list to work from (i.e. $PATH)
# $2 the precise string to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace-path () {
path=$1;
list=$2;
removestr=$3;
replacestr=$4; # Allowed to be ""
export $path=$(echo -n $list | tr ":" "\n" | sed "s|$removestr|$replacestr|" | tr "\n" ":" | sed "s|::|:|g");
unset removestr
return
}
# Removes or replaces an element of $1
#
# $1 name of the shell variable to set (i.e. PATH)
# $2 a ":" delimited list to work from (i.e. $PATH)
# $2 a grep pattern identifying the element to be removed/replaced
# $3 the replacement string (use "" for removal)
function replace-path-pattern () {
path=$1;
list=$2;
removepat=$3;
replacestr=$4; # Allowed to be ""
path-element-by-pattern removestr $list $removepat;
replace-path $path $list $removestr $replacestr;
}
すべての機能でエラーをトラップする必要があります。おそらく、私はそれをしている間は繰り返しパスの解決策に固執すべきです。
を実行して使用します。 /include/path/path_tools.bash
を作業スクリプトに追加し、 replace-path *
関数を呼び出します。
新しい回答またはより良い回答、あるいはその両方を引き続き受け付けています。
これはawkを使用すると簡単です。
交換
{
for(i=1;i<=NF;i++)
if($i == REM)
if(REP)
print REP;
else
continue;
else
print $i;
}
使用して開始
function path_repl {
echo $PATH | awk -F: -f rem.awk REM="$1" REP="$2" | paste -sd:
}
$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_repl /bin /baz
/baz:/usr/bin:/home/js/usr/bin
$ path_repl /bin
/usr/bin:/home/js/usr/bin
追加
指定された位置に挿入します。デフォルトでは、末尾に追加します。
{
if(IDX < 1) IDX = NF + IDX + 1
for(i = 1; i <= NF; i++) {
if(IDX == i)
print REP
print $i
}
if(IDX == NF + 1)
print REP
}
使用して開始
function path_app {
echo $PATH | awk -F: -f app.awk REP="$1" IDX="$2" | paste -sd:
}
$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_app /baz 0
/bin:/usr/bin:/home/js/usr/bin:/baz
$ path_app /baz -1
/bin:/usr/bin:/baz:/home/js/usr/bin
$ path_app /baz 1
/baz:/bin:/usr/bin:/home/js/usr/bin
重複を削除
これは最初の出現を保持します。
{
for(i = 1; i <= NF; i++) {
if(!used[$i]) {
print $i
used[$i] = 1
}
}
}
次のように開始します:
echo $PATH | awk -F: -f rem_dup.awk | paste -sd:
すべての要素が存在するかどうかを検証する
次のコマンドは、ファイルシステムに存在しないすべてのエントリに対してエラーメッセージを出力し、ゼロ以外の値を返します。
echo -n $PATH | xargs -d: stat -c %n
すべての要素がパスであるかどうかを単純に確認し、リターンコードを取得するには、 test
:
echo -n $PATH | xargs -d: -n1 test -d
仮定
echo $PATH
/usr/lib/jvm/java-1.6.0/bin:lib/jvm/java-1.6.0/bin/:/lib/jvm/java-1.6.0/bin/:/usr/lib/qt-3.3/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/tvnadeesh/bin
/lib/jvm/java-1.6.0/bin/を削除する場合は、次のようにします
export PATH=$(echo $PATH | sed 's/\/lib\/jvm\/java-1.6.0\/bin\/://g')
sed
は、 echo $ PATH
から入力を受け取り、/ lib / jvm / java-1.6.0 / bin /:を空に置き換えます
この方法で削除できます
- PATHの順序は変更されていません
- 空のパス、パス内のスペースなどのコーナーケースを優雅に処理します
- dirの部分一致は誤検知を与えません
- PATHの先頭と末尾のパスを適切な方法で処理します。いいえ:ゴミなど。
あなたが持っていると言う / foo:/ some / path:/ some / path / dir1:/ some / path / dir2:/ bar 交換したい / some / path 次に、「/ some / path」を正しく置き換えます;しかし &quot; / some / path / dir1&quot;を残すまたは&quot; / some / path / dir2&quot ;、期待どおり。
function __path_add(){
if [ -d "$1" ] ; then
local D=":${PATH}:";
[ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
}
function __path_remove(){
local D=":${PATH}:";
[ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
}
# Just for the shake of completeness
function __path_replace(){
if [ -d "$2" ] ; then
local D=":${PATH}:";
if [ "${D/:$1:/:}" != "$D" ] ; then
PATH="${D/:$1:/:$2:}";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
fi
fi
}
私は最近、awk/sed/foo などよりも Ruby を使用することを好みます。そこで、カモに対処するための私のアプローチを以下に示します。
# add it to the path
PATH=~/bin/:$PATH:~/bin
export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')
再利用する関数を作成し、
mungepath() {
export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')
}
Ruby のワンライナーのハッシュ、配列、文字列:)
文字列の一部のみを変更するために頭に飛び込むのは、sed置換です。
例: if echo $ PATH =&gt; &quot; / usr / pkg / bin:/ usr / bin:/ bin:/ usr / pkg / games:/ usr / pkg / X11R6 / bin&quot; 次に&quot; / usr / bin&quot;を変更します&quot; / usr / local / bin&quot;へこのようにすることができます:
##は標準出力ファイルを生成します
##&quot; =&quot;スラッシュ(&quot; /&quot;)の代わりに文字が使用されます。 #代替の引用文字はPATHにはありそうもない
##パス区切り文字&quot;:&quot;ここで削除され、再び追加されます。 #最後のパスの後に余分なコロンが必要な場合があります
echo $ PATH | sed '= / usr / bin:= / usr / local / bin:='
このソリューションは、path-element全体を置き換えるため、new-elementが類似している場合は冗長になる可能性があります。
新しいPATHが動的ではなく、常に一定の定数セット内にある場合、これらのを変数に保存し、必要に応じて割り当てることができます:
PATH = $ TEMP_PATH_1; #コマンド...; \ n PATH = $ TEMP_PATH_2; #コマンドなど...
あなたが考えていたものではないかもしれません。 bash / unixの関連コマンドの一部は次のとおりです。
pushd ポップ cd ls#単一の列の場合はl -1A。 見つける grep どのファイルが元のファイルであると確認できるか。 env タイプ
..およびその他すべては、一般的にPATHまたはディレクトリに関連する some を持っています。テキスト変更部分は、さまざまな方法で実行できます!
選択したソリューションには4つの部分があります:
1)パスをそのまま取得する 2)パスをデコードして、変更が必要な部分を見つける 3)必要な変更の決定/それらの変更の統合 4)検証/最終的な統合/変数の設定
dj_segfaultの回答に沿って、複数回実行される可能性のある環境変数を追加/追加するスクリプトでこれを行います:
ld_library_path=${ORACLE_HOME}/lib
LD_LIBRARY_PATH=${LD_LIBRARY_PATH//${ld_library_path}?(:)/}
export LD_LIBRARY_PATH=${ld_library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
この同じ手法を使用して、PATH内のエントリを削除、置換、または操作することは、ファイル名展開のようなパターンマッチングとシェルパラメータ展開のパターンリストサポートを考えると簡単です。