系统信息

系统信息

系统信息

操作系统:OS X

bash:GNU bash,版本 3.2.57(1)-release (x86_64-apple-darwin16)

背景

我希望时间机器从我的所有 git/nodejs 项目中排除一组目录和文件。我的项目目录位于~/code/private/~/code/public/所以我尝试使用 bash 循环来执行tmutil.

问题

简洁版本

如果我有一个计算出的字符串变量k,如何使其在 for 循环中或之前进行通配:

i='~/code/public/*'
j='*.launch'
k=$i/$j # $k='~/code/public/*/*.launch'

for i in $k # I need $k to glob here
do
    echo $i
done

在下面的长版本中,您将看到k=$i/$j.所以我无法在 for 循环中对字符串进行硬编码。

长版

#!/bin/bash
exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
bower_components
build
connect.lock
coverage
dist
e2e/*.js
e2e/*.map
libpeerconnection.log
node_modules
npm-debug.log
testem.log
tmp
typings
'

dirs='
~/code/private/*
~/code/public/*
'

for i in $dirs
do
    for j in $exclude
    do
        k=$i/$j # It is correct up to this line

        for l in $k # I need it glob here
        do
            echo $l
        #   Command I want to execute
        #   tmutil addexclusion $l
        done
    done
done

输出

它们不是通配符。不是我想要的。

~/code/private/*/*.launch                                                                                   
~/code/private/*/.DS_Store                                                                                  
~/code/private/*/.classpath                                                                                 
~/code/private/*/.sass-cache                                                                                
~/code/private/*/.settings                                                                                  
~/code/private/*/Thumbs.db                                                                                  
~/code/private/*/bower_components                                                                           
~/code/private/*/build                                                                                      
~/code/private/*/connect.lock                                                                               
~/code/private/*/coverage                                                                                   
~/code/private/*/dist                                                                                       
~/code/private/*/e2e/*.js                                                                                   
~/code/private/*/e2e/*.map                                                                                  
~/code/private/*/libpeerconnection.log                                                                      
~/code/private/*/node_modules                                                                               
~/code/private/*/npm-debug.log                                                                              
~/code/private/*/testem.log                                                                                 
~/code/private/*/tmp                                                                                        
~/code/private/*/typings                                                                                    
~/code/public/*/*.launch                                                                                    
~/code/public/*/.DS_Store                                                                                   
~/code/public/*/.classpath                                                                                  
~/code/public/*/.sass-cache                                                                                 
~/code/public/*/.settings                                                                                   
~/code/public/*/Thumbs.db                                                                                   
~/code/public/*/bower_components                                                                            
~/code/public/*/build                                                                                       
~/code/public/*/connect.lock                                                                                
~/code/public/*/coverage                                                                                    
~/code/public/*/dist                                                                                        
~/code/public/*/e2e/*.js                                                                                    
~/code/public/*/e2e/*.map                                                                                   
~/code/public/*/libpeerconnection.log                                                                       
~/code/public/*/node_modules                                                                                
~/code/public/*/npm-debug.log                                                                               
~/code/public/*/testem.log                                                                                  
~/code/public/*/tmp                                                                                         
~/code/public/*/typings

答案1

您可以使用 强制进行另一轮评估eval,但这实际上没有必要。 (eval当您的文件名包含特殊字符(如 )时,就会开始出现严重问题$。)问题不在于通配符,而在于波形符扩展。

发生通配符变量扩展,如果变量未加引号,如下(*)

$ x="/tm*" ; echo $x
/tmp

未加引号的扩展发生的另一件事是分词,如果所讨论的模式包含 中的字符(IFS通常是空格),这将是一个问题。为了防止出现此问题,需要通过设置IFS为空字符串来禁用分词。

因此,同样,这与您所做的类似,并且有效:

$ IFS=
$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch
$ i="$HOME/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
/home/foo/public/foo/x.launch

但对于波浪号来说,它不会:

$ i="~/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
~/public/*/*.launch

这是明确记录对于重击:

展开的顺序是:大括号展开;波形符扩展、参数和变量扩展、...

波浪号扩展发生在变量扩展之前,因此变量内的波浪号不会被扩展。简单的解决方法是改用$HOME或完整路径。

(* 从变量扩展 glob 通常不是您想要的)


另一件事:

当您循环模式时,如下所示:

exclude="foo *bar"
for j in $exclude ; do
    ...

请注意,由于$exclude未加引号,因此它既是拆分的,也是通配的。因此,如果当前目录包含与该模式匹配的内容,则会将其扩展为:

$ IFS=
$ i="$HOME/public/foo"
$ exclude="*.launch"
$ touch $i/real.launch
$ for j in $exclude ; do           # glob, no match
    printf "%s\n" "$i"/$j ; done
/home/foo/public/foo/real.launch

$ touch ./hello.launch
$ for j in $exclude ; do           # glob, matches in current dir!
    printf "%s\n" "$i"/$j ; done
/home/foo/public/foo/hello.launch  # not the expected result

要解决此问题,请使用数组变量而不是分割字符串:

$ IFS=
$ exclude=("*.launch")
$ exclude+=("*.not this")
$ for j in "${exclude[@]}" ; do printf "%s\n" "$i"/$j ; done
/home/foo/public/foo/real.launch
/home/foo/public/foo/some file.not this

但请注意,如果模式不匹配任何内容,默认情况下它们将保持原样。因此,如果目录为空,.../*.launch则会打印等。


find -path如果您不介意目标文件应该位于哪个目录级别,则可以使用 完成类似的操作。例如,查找以 结尾的任何路径/e2e/*.js

$ dirs="$HOME/public $HOME/private"
$ pattern="*/e2e/*.js"
$ find $dirs -path "$pattern"
/home/foo/public/one/two/three/e2e/asdf.js

我们必须使用$HOME而不是~出于与以前相同的原因,并且$dirs需要在find命令行上取消引号,以便它被分割,但$pattern应该被引用,这样它就不会被 shell 意外扩展。

(我认为-maxdepth如果你关心的话,你可以使用 GNU find 来限制搜索的深度,但这是一个不同的问题。)

答案2

您可以将其保存为数组而不是字符串,以便稍后在许多情况下使用,并在定义它时让通配发生。在你的情况下,例如:

k=(~/code/public/*/*.launch)
for i in "${k[@]}"; do

或者在后面的示例中,您需要eval一些字符串

dirs=(~/code/private/* ~/code/public/*)
for i in "${dirs[@]}"; do
    for j in $exclude; do
        eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done"
    done
done

答案3

zsh

exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
...
'

dirs=(
~/code/private/*
~/code/public/*
)

for f ($^dirs/${^${=~exclude}}(N)) {
  echo $f
}

${^array}string是展开为$array[1]string $array[2]string....$=var是对变量执行分词(其他 shell 默认情况下会这样做!),$~var对变量进行通配(其他 shell 默认情况下也会这样做(当您通常不希望它们这样做时,您必须$f在上面引用其他外壳))。

(N)是打开的全局限定符空球对于由该扩展产生的每个球体$^array1/$^array2。这使得当它们不匹配时,glob 会扩展为空。这也恰好将一个非 glob 变成~/code/private/foo/Thumbs.db了一个,这意味着如果该特定不存在,则不包括在内。

答案4

旧帖子,但我偶然发现这里,所以认为这可能对其他人有帮助:)

bash 中有一个类似 glob() 的函数...它被称为 compgen -G 它每行输出一个值,因此需要一个 readarray 才能工作。

尝试这个:

i='~/code/public/*'
j='*.launch'
k=$i/$j # $k='~/code/public/*/*.launch'
readarray -t files < <(compgen -G "$k") # $k is globbed here
for i in "${files[@]}"
do
    echo "Found -$i-"
done

相关内容