【问题标题】:How to keep from duplicating path variable in csh如何避免在 csh 中重复路径变量
【发布时间】:2023-04-18 04:06:01
【问题描述】:

通常在您的 cshrc 文件中包含这样的内容来设置路径:

set path = ( . $otherpath $path )

但是,当您多次获取 cshrc 文件时,路径会重复,您如何防止重复?

编辑:这是一种不干净的做法:

set localpaths = ( . $otherpaths )
echo ${path} | egrep -i "$localpaths" >& /dev/null
if ($status != 0) then
    set path = ( . $otherpaths $path )
endif

【问题讨论】:

  • 相关(虽然主要是 bourne shell 答案):*.com/questions/273909/…
  • 您应该将您的方法作为单独的答案发布,而不是作为问题编辑。

标签: path environment-variables csh path-variables


【解决方案1】:

我总是在 .cshrc 中从头开始设置路径。 那就是我从一条基本路径开始,例如:

set path = (. ~/bin /bin /usr/bin /usr/ucb /usr/bin/X11)

(取决于系统)。

然后做:

set path = ($otherPath $path)

添加更多内容

【讨论】:

    【解决方案2】:

    您可以使用以下 Perl 脚本来修剪重复的路径。


    #!/usr/bin/perl
    #
    # ^^ ensure this is pointing to the correct location.
    #
    # Title:    SLimPath
    # Author:   David "Shoe Lace" Pyke <eselle@users.sourceforge.net >
    #   :   Tim Nelson 
    # Purpose: To create a slim version of my envirnoment path so as to eliminate
    #       duplicate entries and ensure that the "." path was last.
    # Date Created: April 1st 1999
    # Revision History:
    #   01/04/99: initial tests.. didn't wok verywell at all
    #       : retreived path throught '$ENV' call
    #   07/04/99: After an email from Tim Nelson <wayland@ne.com.au> got it to
    #         work.
    #       : used 'push' to add to array
    #       : used 'join' to create a delimited string from a list/array.
    #   16/02/00: fixed cmd-line options to look/work better
    #   25/02/00: made verbosity level-oriented
    #
    #
    
    use Getopt::Std;
    
    sub printlevel;
    
    $initial_str = "";
    $debug_mode = "";
    $delim_chr = ":";
    $opt_v = 1;
    
    getopts("v:hd:l:e:s:");
    
    OPTS: {
        $opt_h && do {
    print "\n$0 [-v level] [-d level] [-l delim] ( -e varname | -s strname | -h )";
    print "\nWhere:";
    print "\n   -h  This help";
    print "\n   -d  Debug level";
    print "\n   -l  Delimiter (between path vars)";
    print "\n   -e  Specify environment variable (NB: don't include \$ sign)";
    print "\n   -s  String (ie. $0 -s \$PATH:/looser/bin/)";
    print "\n   -v  Verbosity (0 = quiet, 1 = normal, 2 = verbose)";
    print "\n";
            exit;
        };
        $opt_d && do {
            printlevel 1, "You selected debug level $opt_d\n";
            $debug_mode = $opt_d;
        };
        $opt_l && do {
            printlevel 1, "You are going to delimit the string with \"$opt_l\"\n";
            $delim_chr = $opt_l;
        };
        $opt_e && do {
            if($opt_s) { die "Cannot specify BOTH env var and string\n"; }
            printlevel 1, "Using Environment variable \"$opt_e\"\n";
            $initial_str = $ENV{$opt_e};
        };
        $opt_s && do {
            printlevel 1, "Using String \"$opt_s\"\n";
            $initial_str = $opt_s;
        };
    }
    
    if( ($#ARGV != 1) and !$opt_e and !$opt_s){
        die "Nothing to work with -- try $0 -h\n";
    }
    
    $what = shift @ARGV;
    # Split path using the delimiter
    @dirs = split(/$delim_chr/, $initial_str);
    
    $dest;
    @newpath = ();
    LOOP: foreach (@dirs){
        # Ensure the directory exists and is a directory
        if(! -e ) { printlevel 1, "$_ does not exist\n"; next; }
        # If the directory is ., set $dot and go around again
        if($_ eq '.') { $dot = 1; next; }
    
    #   if ($_ ne `realpath $_`){
    #           printlevel 2, "$_ becomes ".`realpath $_`."\n";
    #   }
        undef $dest;
        #$_=Stdlib::realpath($_,$dest);
        # Check for duplicates and dot path
        foreach $adir (@newpath) { if($_ eq $adir) { 
            printlevel 2, "Duplicate: $_\n";
            next LOOP; 
        }}
    
        push @newpath, $_;
    }
    
    # Join creates a string from a list/array delimited by the first expression
    print join($delim_chr, @newpath) . ($dot ? $delim_chr.".\n" : "\n");
    
    printlevel 1, "Thank you for using $0\n";
    exit;
    
    sub printlevel {
        my($level, $string) = @_;
    
        if($opt_v >= $level) {
            print STDERR $string;
        }
    }
    

    希望对你有用。

    【讨论】:

    • 哎呀.. 没有意识到我把我所有的 cmets 都留在里面了.. 一切都很好。 (顺便说一句,再次感谢蒂姆):)
    【解决方案3】:

    好的,在 csh 中 不是,但这就是我在 bash 中将 $HOME/bin 附加到我的路径的方式...

    case $PATH in
        *:$HOME/bin | *:$HOME/bin:* ) ;;
        *) export PATH=$PATH:$HOME/bin
    esac
    

    调味...

    【讨论】:

      【解决方案4】:

      十年来我一直在使用以下 (Bourne/Korn/POSIX/Bash) 脚本:

      :   "@(#)$Id: clnpath.sh,v 1.6 1999/06/08 23:34:07 jleffler Exp $"
      #
      #   Print minimal version of $PATH, possibly removing some items
      
      case $# in
      0)  chop=""; path=${PATH:?};;
      1)  chop=""; path=$1;;
      2)  chop=$2; path=$1;;
      *)  echo "Usage: `basename $0 .sh` [$PATH [remove:list]]" >&2
          exit 1;;
      esac
      
      # Beware of the quotes in the assignment to chop!
      echo "$path" |
      ${AWK:-awk} -F: '#
      BEGIN   {   # Sort out which path components to omit
                  chop="'"$chop"'";
                  if (chop != "") nr = split(chop, remove); else nr = 0;
                  for (i = 1; i <= nr; i++)
                      omit[remove[i]] = 1;
              }
      {
          for (i = 1; i <= NF; i++)
          {
              x=$i;
              if (x == "") x = ".";
              if (omit[x] == 0 && path[x]++ == 0)
              {
                  output = output pad x;
                  pad = ":";
              }
          }
          print output;
      }'
      

      在 Korn shell 中,我使用:

      export PATH=$(clnpath /new/bin:/other/bin:$PATH /old/bin:/extra/bin)
      

      这给我留下了 PATH 前面包含新的和其他 bin 目录,加上主路径值中每个目录名称的一份副本,除了旧的和额外的 bin 目录已删除 bin。

      您必须将其改编为 C shell(抱歉 - 但我非常相信 C Shell Programming Considered Harmful 阐明的真理)。首先,您不必摆弄冒号分隔符,因此生活实际上更轻松。

      【讨论】:

        【解决方案5】:

        好吧,如果您不在乎路径的顺序,您可以执行以下操作:

        set path=(`echo $path | tr ' ' '\n' | sort | uniq | tr '\n' ' '`)
        

        这将对您的路径进行排序并删除任何相同的额外路径。如果你有 。在您的路径中,您可能需要使用 grep -v 将其删除并在末尾重新添加。

        【讨论】:

        • 我看到的唯一问题是它会改变路径顺序,但我确实喜欢它是单线的。
        【解决方案6】:

        这是一个没有排序的长单行:
        设置路径 = (echo $path | tr ' ' '\n' | perl -e 'while (&lt;&gt;) { print $_ unless $s{$_}++; }' | tr '\n' ' ')

        【讨论】:

          【解决方案7】:

          peper 博士,

          我通常更喜欢坚持我所居住的 shell 的脚本功能。使其更便携。所以,我喜欢您使用 csh 脚本编写的解决方案。我只是将它扩展为在 localdirs 中的每个目录上工作,以使其适合我自己。

          foreach 目录 ( $localdirs ) 回声 ${路径} | egrep -i "$dir" >& /dev/null 如果 ($status != 0) 那么 设置路径 = ( $dir $path ) 万一 结尾

          【讨论】:

            【解决方案8】:

            我很惊讶没有人使用tr ":" "\n" | grep -x 技术来搜索给定文件夹是否已存在于 $PATH 中。有什么理由不这样做?

            在 1 行中:

            if ! $(echo "$PATH" | tr ":" "\n" | grep -qx "$dir") ; then PATH=$PATH:$dir ; fi
            

            这是我让自己一次将多个文件夹添加到 $PATH 的函数(使用“aaa:bbb:ccc”符号作为参数),在添加之前检查每个文件夹是否重复:

            append_path()
            {
                local SAVED_IFS="$IFS"
                local dir
                IFS=:
                for dir in $1 ; do
                    if ! $( echo "$PATH" | tr ":" "\n" | grep -qx "$dir" ) ; then
                        PATH=$PATH:$dir
                    fi
                done
                IFS="$SAVED_IFS"
            }
            

            它可以在这样的脚本中调用:

            append_path "/test:$HOME/bin:/example/my dir/space is not an issue"
            

            具有以下优点:

            • 没有 bashism 或任何特定于 shell 的语法。它与!#/bin/sh 完美运行(我用破折号测试过)
            • 可以一次添加多个文件夹
            • 不排序,保留文件夹顺序
            • 完美处理文件夹名称中的空格
            • 无论 $folder 是在开始、结束、中间还是 $PATH 中的唯一文件夹,单个测试都有效(从而避免测试 x:*、*:x、:x: , x,就像这里的许多解决方案都隐含地做的那样)
            • 如果 $PATH 以“:”开头或结尾,或者其中包含“::”(表示当前文件夹),则有效(并保留)
            • 不需要awksed
            • EPA 友好 ;) 原始 IFS 值被保留,所有其他变量都是函数范围的本地变量。

            希望有帮助!

            【讨论】:

            • +!这基本上就是我一直在做的事情,而且效果很好!
            【解决方案9】:

            使用 sed(1) 删除重复项。

            $ PATH=$(echo $PATH | sed -e 's/$/:/;s/^/:/;s/:/::/g;:a;s#\(:[^:]\{1,\}:\)\(.*\)\1#\1\2#g;ta;s/::*/:/g;s/^://;s/:$//;')
            

            这将在第一个实例之后删除重复项,这可能是也可能不是您想要的,例如:

            $ NEWPATH=/bin:/usr/bin:/bin:/usr/local/bin:/usr/local/bin:/bin
            $ echo $NEWPATH | sed -e 's/$/:/; s/^/:/; s/:/::/g; :a; s#\(:[^:]\{1,\}:\)\(.*\)\1#\1\2#g; t a; s/::*/:/g; s/^://; s/:$//;'
            /bin:/usr/bin:/usr/local/bin
            $
            

            享受吧!

            【讨论】:

              【解决方案10】:

              这是我使用的——也许其他人会觉得它有用:

              #!/bin/csh
              #  ABSTRACT
              #    /bin/csh function-like aliases for manipulating environment
              #    variables containing paths.
              #
              #  BUGS
              #    - These *MUST* be single line aliases to avoid parsing problems apparently related
              #      to if-then-else
              #    - Aliases currently perform tests in inefficient in order to avoid parsing problems
              #    - Extremely fragile - use bash instead!!
              #
              #  AUTHOR
              #    J. P. Abelanet - 11/11/10
              
              #  Function-like alias to add a path to the front of an environment variable
              #    containing colon (':') delimited paths, without path duplication
              #
              #  Usage: prepend_path ENVVARIABLE /path/to/prepend
              alias prepend_path \
                'set arg2="\!:2";  if ($?\!:1 == 0) setenv \!:1 "$arg2";  if ($?\!:1 && $\!:1 !~ {,*:}"$arg2"{:*,}) setenv \!:1 "$arg2":"$\!:1";'
              
              #  Function-like alias to add a path to the back of any environment variable 
              #    containing colon (':') delimited paths, without path duplication
              #
              #  Usage: append_path ENVVARIABLE /path/to/append
              alias append_path \
                'set arg2="\!:2";  if ($?\!:1 == 0) setenv \!:1 "$arg2";  if ($?\!:1 && $\!:1 !~ {,*:}"$arg2"{:*,}) setenv \!:1 "$\!:1":"$arg2";'
              

              【讨论】:

                【解决方案11】:

                我的需求与原始问题相同。 基于您之前的答案,我在 Korn/POSIX/Bash 中使用过:

                export PATH=$(perl -e 'print join ":", grep {!$h{$_}++} split ":", "'$otherpath:$PATH\")
                

                我很难直接在 csh 中翻译它(csh 转义规则太疯狂了)。我使用过(如 dr_pepper 所建议的):

                set path = ( `echo $otherpath $path | tr ' ' '\n' | perl -ne 'print $_ unless $h{$_}++' | tr '\n' ' '`)
                

                您是否有进一步简化它的想法(减少管道数量)?

                【讨论】:

                  【解决方案12】:

                  在 csh 中设置 path(小写,csh 变量)而不是 PATH(环境变量)时,可以使用 set -f 和 set -l,这将只保留每个列表元素出现一次(更喜欢保留分别是第一个或最后一个)。

                  https://nature.berkeley.edu/~casterln/tcsh/Builtin_commands.html#set

                  像这样的

                  cat foo.csh # or .tcshrc or whatever: set -f path = (/bin /usr/bin . ) # initial value set -f path = ($path /mycode /hercode /usr/bin ) # add things, both new and duplicates

                  不会在每次source 时都使用重复扩展 PATH:

                  % source foo.csh % echo $PATH % /bin:/usr/bin:.:/mycode:/hercode % source foo.csh % echo $PATH % /bin:/usr/bin:.:/mycode:/hercode

                  set -f there 确保只保留每个 PATH 元素的第一次出现。

                  【讨论】: