32

PATH のようなシェル変数から要素を削除する慣用的な方法はありますか?

それは私が取りたいです

PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.

残りの変数を破壊することなく、を削除または置換します。新しい要素を任意の位置に配置できる/path/to/app/binようにするための追加のポイント。ターゲットは明確に定義された文字列によって認識され、リスト内の任意の場所に出現する可能性があります。

私はこれが行われたのを見たことがあることを知っており、おそらく自分で何かをまとめることができますが、私は良いアプローチを探しています. 移植性と標準化はプラスです。

私は bash を使用していますが、お気に入りのシェルでも例を歓迎します。


ここでのコンテキストは、数十の実行可能ファイルを生成し、ファイルシステムにデータを隠し、環境変数を使用する大規模な科学分析パッケージの複数のバージョン (分析を行うためのものと、フレームワークで作業するためのもの) を便利に切り替える必要があるというものです。これらすべてのものを見つけるのに役立ちます。バージョンを選択するスクリプトを書きたいのですが$PATH、現在アクティブなバージョンに関連する要素を削除して、新しいバージョンに関連する同じ要素に置き換える必要があります。


$PATHこれは、ログイン スクリプトなどを再実行するときに要素の繰り返しを防止するという問題に関連しています。


4

12 に答える 12

20

dmckee からの提案されたソリューションへの対処:

  1. Bash の一部のバージョンでは関数名にハイフンを使用できますが、他のバージョン (MacOS X) では使用できません。
  2. 関数の終了直前に return を使用する必要はないと思います。
  3. すべてのセミコロンが必要だとは思いません。
  4. path-element-by-pattern で値をエクスポートする理由がわかりません。グローバル変数を設定する (または作成する) ことと同等と考えexportてください。これは、可能な限り避けるべきものです。
  5. あなたが ' 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 は (完全な) パス要素として表示されず、パス要素の一部としてのみ表示されるため、元のパスが返されることを期待していました。

これは、コマンドreplace-pathの 1 つを変更することで修正できます。sed

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\$")

ちなみに、これは標準ではないことに注意してくださいgrep -m 1(これは GNU 拡張であり、MacOS X でも利用できます)。実際、-nオプション forechoも非標準です。改行をエコーからコロンに変換することによって追加された末尾のコロンを単純に削除する方がよいでしょう。path-element-by-pattern は 1 回だけ使用され、望ましくない副作用 ( と呼ばれる既存のエクスポートされた変数を破壊する$removestr) があるため、適切にその本体に置き換えることができます。これは、スペースや不要なファイル名の拡張による問題を回避するために引用符をより自由に使用すると、次のようになります。

# 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\$")
    replace_path "$path" "$list" "$removestr" "$replacestr"
}

PATH のような変数の問題をデバッグするときに役立つPerl スクリプトがechopathあります。

#!/usr/bin/perl -w
#
#   "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $"
#
#   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";
    }
}

以下のテスト コードで変更したソリューションを実行すると、次のようになります。

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

出力は次のとおりです。

.
/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 LD_LIBRARY_PATHが評価されることに注意してください$LD_LIBRARY_PATH。あなたの関数がそれを行うことができれば、ユーザーは次のように入力できます:

replace_path PATH /usr/bin /work/bin

これは、次を使用して実行できます。

list=$(eval echo '$'$path)

これにより、コードが次のようにリビジョンされます。

# 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 '$'$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 '$'$path)
    removepat=$2
    replacestr=$3            # Allowed to be empty or unset

    removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
    replace_path "$path" "$removestr" "$replacestr"
}

次の改訂されたテストも機能するようになりました。

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

以前と同じ出力が生成されます。

于 2008-11-16T15:53:15.810 に答える
7

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[*]}");
于 2008-12-17T02:18:03.340 に答える
3

要素を削除するには、sed を使用できます。

#!/bin/bash
NEW_PATH=$(echo -n $PATH | tr ":" "\n" | sed "/foo/d" | tr "\n" ":")
export PATH=$NEW_PATH

「foo」を含むパスをパスから削除します。

sed を使用して、特定の行の前後に新しい行を挿入することもできます。

編集: sort と uniq を介してパイプすることで重複を削除できます:

echo -n $PATH | tr ":" "\n" | sort | uniq -c | sed -n "/ 1 / s/.*1 \(.*\)/\1/p" | sed "/foo/d" | tr "\n" ":"
于 2008-11-07T23:31:30.047 に答える
2

「 cshでパス変数を複製しないようにする方法」の回答には、関連するプログラムがいくつかあります。繰り返される要素がないことを確認することに重点を置いていますが、私が提供するスクリプトは次のように使用できます。

export PATH=$(clnpath $head_dirs:$PATH:$tail_dirs $remove_dirs)

$ head_dirsに1つ以上のディレクトリがあり、$ tail_dirsに1つ以上のディレクトリがあり、$ remove_dirsに1つ以上のディレクトリがあるとすると、シェルを使用して、ヘッド、現在、およびテールの部分を大規模な値に連結し、それぞれを削除します。結果から$remove_dirsにリストされているディレクトリの数(存在しない場合はエラーではありません)、およびパス内のディレクトリの2番目以降の出現を排除します。

これは、パスコンポーネントを特定の位置に配置することには対応していません(最初または最後を除き、間接的にのみ)。表記上、新しい要素を追加する場所、または置き換える要素を指定するのは面倒です。

于 2008-11-08T06:18:13.033 に答える
2

bash自体が検索と置換を実行できることに注意してください。通常の「1回またはすべて」、大文字と小文字を区別しないオプションをすべて実行できます。

マニュアルページから:

${parameter/pattern/string}

パス名の展開と同様に、パターンが展開されてパターンが生成されます。パラメータが展開され、その値に対するパターンの最長一致が文字列に置き換えられます。Ipattern が で始まる場合/、パターンのすべての一致が文字列に置き換えられます。通常、最初の一致のみが置き換えられます。パターンが で始まる場合#、パラメータの展開された値の先頭で一致する必要があります。パターンが で始まる場合%、パラメータの展開された値の最後で一致する必要があります。文字列が null の場合、一致するパターンは削除され、/次のパターンは省略される場合があります。パラメータが@または*の場合、置換操作は各位置パラメータに順番に適用され、展開は結果のリストです。パラメータが添字付きの配列変数の場合@または *、置換操作は配列の各メンバーに順番に適用され、展開は結果のリストです。

$IFS(入力フィールドセパレーター) を目的の区切り文字に設定することで、フィールド分割を行うこともできます。

于 2008-11-16T17:54:29.763 に答える
1

仮定する

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 /:を空に置き換えます

このようにして、削除することができます

于 2011-09-29T07:02:09.797 に答える
1

これは 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
于 2008-12-06T22:00:26.967 に答える
1
  • PATHの順番が乱れない
  • 空のパス、パス内のスペースなどのコーナーケースを適切に処理します
  • dirの部分的な一致は、誤った肯定的なものを与えません
  • PATH の先頭と末尾のパスを適切に処理します。いいえゴミなど。

/foo:/some/path:/some/path/dir1:/some/path/dir2:/bar があり、/some/path を置き換えたいとします。その後、"/some/path" は正しく置き換えられますが、"/ some/path/dir1" または "/some/path/dir2" は、期待どおりです。

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  
}  

関連記事 Bash で $PATH 変数からパスを削除する最もエレガントな方法は何ですか?

于 2012-08-13T17:21:40.107 に答える
1

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作業スクリプトでa を実行し、replace-path*関数を呼び出すことで使用します。


私はまだ新しいおよび/またはより良い答えを受け入れています。

于 2008-11-08T18:46:12.993 に答える
0

dj_segfault's answerに沿って、複数回実行される可能性のある環境変数を追加/先頭に追加するスクリプトでこれを行います。

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 のエントリを削除、置換、または操作することは、ファイル名展開のようなパターン マッチングとシェル パラメーター展開のパターン リスト サポートを考えると簡単です。

于 2014-09-09T16:19:45.343 に答える
0

文字列の一部だけを変更するために頭に浮かんだ最初のことは、sed 置換です。

例: echo $PATH => "/usr/pkg/bin:/usr/bin:/bin:/usr/pkg/games:/usr/pkg/X11R6/bin" の場合、"/usr/bin" を次のように変更します。 「/usr/local/bin」は次のように実行できます。

## 標準出力ファイルを生成します

## スラッシュ ("/") の代わりに "=" 文字が使用されます。

## パス区切り文字 ":" は削除され、ここで再度追加されます。 # 最後のパスの後に余分なコロンが必要になる場合があります

エコー $PATH | sed '=/usr/bin:=/usr/local/bin:='

このソリューションは path-element 全体を置き換えるため、new-element が類似している場合は冗長になる可能性があります。

新しい PATH が動的ではなく、常に何らかの定数セット内にある場合は、それらを変数に保存し、必要に応じて割り当てることができます。

パス=$TEMP_PATH_1; # コマンド ... ; \n パス=$TEMP_PATH_2; # コマンドなど... ;

あなたが考えていたものではないかもしれません。bash/unix での関連コマンドのいくつかは次のようになります。

pushd popd cd ls # 単一の列の場合は l -1A かもしれません。そのファイルがどこから来たと思われるかを # 確認できる grep を見つけます。環境タイプ

..そしてそれ以上のものは一般的にPATHまたはディレクトリに関係しています。テキストの変更部分は、さまざまな方法で実行できます。

どのソリューションを選択しても、次の 4 つの部分があります。

1) パスをそのまま取得する 2) パスをデコードして、変更が必要な部分を見つける 3) 必要な変更を決定する/それらの変更を統合する 4) 検証/最終的な統合/変数を設定する

于 2008-11-08T00:15:15.183 に答える