web-dev-qa-db-ja.com

シェルスクリプトで$ PATH要素を操作するにはどうすればよいですか?

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

それは私が取りたいです

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

およびremoveまたはreplace/path/to/app/binを、残りを壊さずに変数の。 put新しい要素を任意の位置に配置できるようにするための追加のポイント。ターゲットは明確に定義された文字列によって認識可能であり、リストの任意の場所で発生する可能性があります。

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

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


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


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


29
dmckee

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は(完全な)パス要素として表示されず、パス要素の一部としてのみ表示されるため、元のパスを取り戻すことを期待していました。

これは、sedコマンドの1つを変更することで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\$")

grep -m 1は標準ではないことに注意してください(これはGNU拡張機能であり、MacOS Xでも利用可能です)。実際、echo-nオプションも非標準です。 ;改行をエコーからコロンに変換することで追加された末尾のコロンを削除する方がよいでしょう。path-element-by-patternは一度だけ使用されるため、望ましくない副作用があります(以前のコードをすべて覆い隠します)。 $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"
}

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

#!/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

以前と同じ出力を生成します。

19

私の答えを 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[*]}");
6
nicerobot

要素を削除するには、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" ":"
3
florin

Bash自体が検索と置換を実行できることに注意してください。それはあなたが期待するであろうすべての通常の「一度またはすべて」、ケースに敏感なオプションを行うことができます。

マニュアルページから:

$ {パラメータ/パターン/文字列}

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

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

2
dj_segfault

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

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

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

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

2

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*関数を呼び出すことで使用します。


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

1
dmckee

最近は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ワンライナー:)のハッシュ、配列、文​​字列

1
Steeve McCauley
  • 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変数からパスを削除する最もエレガントな方法は何ですか?

1
GreenFox

仮定します

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')

sedecho $PATHから入力を受け取り、/ lib/jvm/Java-1.6.0/bin /:を空に置き換えます

このようにして削除できます

1
nash

これは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

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のエントリを削除、置換、または操作するのは簡単です。

0
derekm

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

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

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

##スラッシュ( "/")の代わりに "="文字が使用されるのは面倒なので、#代替引用文字がPATHに含まれる可能性は低いはずです。

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

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

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

新しいPATH'-sが動的ではないが、常に一定のセット内にある場合は、thoseを変数に保存し、必要に応じて割り当てることができます。

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

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

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

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

選択したソリューションには、次の4つの部分があります。

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

0
waynecolvin