如何递归地将子目录添加到PATH中?

21

你是如何做到的?我在工作中的code/目录中,按文件夹、子文件夹和子子文件夹的顺序组织了所有脚本或程序,以便于我定期运行。

9个回答

22
在你的脚本结尾处加上这一行代码:
PATH=${PATH}:$(find ~/code -type d | tr '\n' ':' | sed 's/:$//')
这将在当前路径下附加您~/code树中的每个目录。我不喜欢这个想法,更喜欢只有几个目录保存我的可执行文件,并明确列出它们,但因人而异。 如果您想排除所有隐藏的目录,基本上需要剥离每个具有序列"/."的行(以确保您不会检查隐藏目录下的子目录):
PATH=${PATH}:$(find ~/code -type d | sed '/\/\\./d' | tr '\n' ':' | sed 's/:$//')

这将防止您获取像~/code/level1/.hidden/level3/这样的目录(即,一旦检测到它们是隐藏的,就停止在子树中搜索)。如果您只想排除隐藏的目录,但仍允许它们下面的非隐藏目录,请使用:

PATH=${PATH}:$(find ~/code -type d -name '[^\.]*' | tr '\n' ':' | sed 's/:$//')

这将允许~/code/level1/.hidden2/level3/,但不允许~/code/level1/.hidden2/.hidden3/,因为-name只检查文件的基本名称,而不是完整的路径名。


1
你可能想在这里使用 find -print0 | tr "\0" ":",Pax,这样你就有了一个过滤器,可以处理所有有效的Unix路径(包括其中带有问题的换行符的路径)。 - Kent Fredric
3
@Kent:非打印字符(包括空格)是邪恶的,如果我找到使用它们的人,我会用我整个IBM大型机COBOL手册的集合将他们打死 :-) - paxdiablo
1
如果你想要一些娱乐,可以在目录名中加入一个哔声字符。这样,当有人进入该目录时,你就会知道了 ;) - Kent Fredric
1
@Charles,然后将其投下反对票并投赞成票给另一个答案。 “被接受”的(最佳解决问题者的问题)和“最高得票”的(我希望大多数人都认为是社区投票最好的)之间存在差异。事实上,我以前提出过一个请求,希望有一个视图,其中最高得票的答案排在“被接受”的答案之上,但遭到了拒绝。当然,这对你在这里没有帮助,因为这个答案既是最高得票的,也是被接受的,但这可能不会永远是这种情况。 - paxdiablo
请参见http://meta.stackexchange.com/questions/60777/modified-answer-view-based-only-on-votes-not-accepted-status - 嗯,不是真的被打回,但也没有采取行动,这两者差不多 :-) 但是我非常认真地希望你给我点个踩。如果你认为它没有帮助,我宁愿你这样做。我不会受到冒犯,或因失去2分而受影响:-),这将有望使一个更好的答案(假设你的观点是正确的,我不会对此发表评论)在投票中脱颖而出。 - paxdiablo
显示剩余8条评论

18

以下代码可以正确地处理隐藏目录及其子目录,并能够处理名字中包含换行符或其他空白字符的情况,同时也会将空白字符去除:

export PATH="${PATH}$(find ~/code -name '.*' -prune -o -type d -printf ':%p')"

我使用类似的技巧来自动设置CLASSPATH


2
非常酷,没有调用sed,因为find会为您处理一切......除了我没有printf版本的find :-/ - user50264

2
如果你真的需要这么做,你可以尝试进一步减少那个PATHs列表:删除不包含可执行文件的文件夹。当然,这样会付出更多的统计数据的代价。 ;-/
PATH=$PATH$(find ~/code -name '.*' -prune -o -type f -a -perm /u+x -printf ':%h\n' | sort | uniq | tr -d '\n')

我建议您避免在每次打开终端时执行此操作。应该使用某种缓存方式。例如,将以下行添加到您的~/.bashrc文件中:
[ -s ~/.codepath ] && export PATH=$PATH$(<~/.codepath)

并运行

find ~/code -name '.*' -prune -o -type f -a -perm /u+x -printf ':%h\n' |sort |uniq |tr -d '\n' > ~/.codepath

只有当你确实知道某些事情发生了变化时,才需要进行操作。
编辑:以下是一个重新编写的命令,包含你遗漏的-printf。
find ~/code -name '.*' -prune -o -type f -a -perm /u+x -print | sed 's@/[^/]\+$@:@' | sort | uniq | tr -d '\n' | sed 's/^/:/; s/:$//'

2

类似于这样的东西

my_path=$(find $root -type d | tr '\n' ':')

或者

my_path=$(find $root -type d -printf '%p:')

非常好。有没有办法排除隐藏目录?我有一个git仓库,所以尝试后发现有比我预期的更多的目录 :-/ - user50264
1
没有必要在“\n”中使用查找打印原因 - 最好一开始就告诉它用冒号分隔文件。 - Charles Duffy
@user50264 - 你可以像在git中排除其他内容一样进行排除;例如:find "$root" -name ".[a-z]*" -prune -o -type d -printf '%p:',以排除与给定模式匹配的点文件(排除.是不明智的,而给定的技巧是一个可行的80%解决方案,以避免它)。 - Charles Duffy

1

我也一直在寻找解决这个问题的方法。如果bash有一种方式可以指定对于某些路径,你希望它递归地搜索文件,那将是非常好的。例如:

PATH="$PATH:/usr/local/bin:~/bin**"

~/bin 会搜索该目录及其所有子目录,而不会使您的 PATH 变量混乱。

由于这个功能没有实现,我的临时解决方案是将所有内容放在我的 bin 目录中,然后创建另一个目录 "bindir",其中包含指向 "bin" 中实际可执行文件的符号链接,但它们被整齐地排列到子目录中,以便更容易找到它们。

我的唯一问题是是否应该使用硬链接而不是符号链接。


1
在Bash 4.0中,您可以直接使用新支持的**运算符。您需要先在某些地方启用它:
shopt -s globstar

你可以接着做

echo ** 

递归地回显当前目录下所有后代文件。

注意,有时它会在过于复杂的目录中退出,所以请在最低的递归点使用 **。

echo **/  

巧合的是,递归地发出所有目录名称,仅限目录名称。(不包括当前目录)

echo ./**/ 

包括当前目录。(顺便提一下,它也跳过隐藏的目录)

因此,这应该适用于创建路径字符串:

echo ./**/ | sed 's/\s\s*/:/g'

如果您不想使用相对路径,

echo $PWD/**/ | sed 's/\s\s*/:/g' 

确认

从您在其他帖子中的评论中可以看出,您想要类似于“确认”提供的行为。如果您打算使用查找+grep组合,则此工具通常更高效且更易于用于此任务。

示例:

# search for 'mystring' in all c++ files recursively ( excluding SCM dirs and backup files ) 
ack  "mystring" --type=cpp 

# finds all text files not in an SCM dir ( recursively) and not a backup using type heuristics. 
ack -f --type=text  

我喜欢这个解决方案的简洁性,但是我觉得我可能有一个不同版本的sed,它会用冒号替换每个目录中的第一个"s"。但是感谢你让我升级到bash 4.0 :) - user50264
@Charles:你有测试过这个假设吗?你对“标准Sed”的定义是什么?我的处理\s很好。echo "hello world" | sed 's/\s/x/' 会输出helloxworld - Kent Fredric
Bah,OpenBSD的sed就像你所说的那样,不友好于\s。请使用[[:space:]]代替\s :( - Kent Fredric
@Kent - “标准”指符合POSIX标准而没有扩展。你知道,在UNIX上控制sed最小功能的标准应该是什么样子的。 - Charles Duffy
此外,这个答案是完全错误的——如果您的文件名中有空格会发生什么?以下代码修复了这个问题:paths=( **/ ); oIFS="$IFS"; IFS=':'; CLASSPATH=${paths[*]}; IFS="$oIFS" - Charles Duffy
显示剩余3条评论

0

类似这样:

_path="$(
  find <path> -name '.*' -prune -o -type d -print
  )" 

[[ $_path ]] && _path="${_path//$'\n'/:}" PATH="$PATH:${_path%:}"   

如果您拥有GNU find,您可以直接使用-printf ':%p'

0
尝试这种方式:
export PATH="$PATH:$(du "~/code/" | cut -f2 | tr '\n' ':' | sed 's/:*$//')"
这将把~/code及其所有子目录添加到$PATH中
解释:
- du会显示每行的所有子目录信息
- cut -f2将提取第二列,即子目录的名称
- tr '\n' ':'将每个换行符更改为冒号。这将把所有行连接成一行,并且子目录由冒号分隔
- sed 's/:*$//'将删除最后一个冒号

0

我有一个单独的bin目录$HOME/bin,里面安装了我构建的任何程序的副本(或脚本、或指向程序或脚本的符号链接)。目前它里面有近 600 个命令(ls | wc -l 统计出来是 604,但由于各种原因,还有十几个子目录)。

当我测试程序时,我会在构建它的地方执行它;一旦我暂时完成测试,我会用我的acquire脚本来获取它,该脚本将文件复制并设置其权限。

这样留给我的就是一个整洁漂亮的配置文件(我不使用.bashrc;我宁愿在登录 shell 启动时进行一次设置,子 shell 继承工作环境而无需再解析.bashrc),但却是一个相当笨重的bin目录。例如,它避免了每次 shell 启动时重置 PATH 的成本,鉴于我的路径设置代码多么复杂,这真是太好了!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接