web-dev-qa-db-ja.com

bash globを文字列変数にする方法は?

システム情報

OS:OS X

bash:GNU bash、バージョン3.2.57(1)-release(x86_64-Apple-darwin16)

バックグラウンド

タイムマシンで、すべてのgit/nodejsプロジェクトから一連のディレクトリとファイルを除外します。私のプロジェクトディレクトリは~/code/private/および~/code/public/そのため、tmutilを実行するためにbashループを使用しようとしています。

問題

短縮版

calculated文字列変数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
14
John Siu

evalを使用して別の評価ラウンドを強制できますが、それは実際には必要ありません。 (そして、evalは、ファイル名に_$_のような特殊文字が含まれているとすぐに深刻な問題を起こし始めます。)問題は、グロビングではなく、チルダ展開にあります。

変数がここにあるように引用符で囲まれていない場合、after変数展開のグロブが発生(*)

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

したがって、同じように、これはあなたがしたことと似ており、機能します。

_$ 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
_

これは 明確に文書化されています Bashの場合:

展開の順序は次のとおりです。チルダ展開、パラメーターおよび変数展開、...

ティルダ展開は変数展開の前に行われるため、変数内のティルダは展開されません。簡単な回避策は、代わりに_$HOME_または絶対パスを使用することです。

(*変数からグロブを展開することは通常あなたが望むものではありません)


別物:

パターンをループすると、次のようになります。

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

_$exclude_は引用符で囲まれていないため、この時点で分割され、またグロブされることに注意してください。したがって、現在のディレクトリにパターンに一致するものが含まれている場合は、次のように展開されます。

_$ i="$HOME/public/foo"
$ exclude="*.launch"
$ touch $i/real.launch
$ for j in $exclude ; do           # split and glob, no match
    echo "$i"/$j ; done
/home/foo/public/foo/real.launch

$ touch ./hello.launch
$ for j in $exclude ; do           # split and glob, matches in current dir!
    echo "$i"/$j ; done
/home/foo/public/foo/hello.launch  # not the expected result
_

これを回避するには、分割された文字列の代わりに 配列変数 を使用します。

_$ exclude=("*.launch")
$ exclude+=("something else")
$ for j in "${exclude[@]}" ; do echo "$i"/$j ; done
/home/foo/public/foo/real.launch
/home/foo/public/foo/something else
_

追加のボーナスとして、配列エントリには、分割の問題なしに空白を含めることもできます。


ターゲットファイルのディレクトリレベルを気にしない場合は、_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_はシェルによって誤って展開されないように、引用されます。

(GNU findで_-maxdepth_を試してみて、必要に応じて検索の深さを制限することもできますが、それは少し別の問題です。)

19
ilkkachu

これを文字列ではなく配列として保存し、後で多くの場合に使用して、定義時にグロビングを発生させることができます。あなたの場合、例えば:

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
4
Eric Renouf

@ilkkachuの回答により、主要なグロビング問題が解決されました。彼への完全な信用。

V1

ただし、excludeにはワイルドカード(*)がある場合とない場合の両方のエントリが含まれており、すべてに存在しない可能性があるため、$i/$jの展開後に追加のチェックが必要です。ここで私の発見を共有しています。

#!/bin/bash
exclude="
*.launch
.DS_Store
.classpath
.sass-cache
.settings
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="
$HOME/code/private/*
$HOME/code/public/*
"

# loop $dirs
for i in $dirs; do
    for j in $exclude ; do
        for k in $i/$j; do
            echo -e "$k"
            if [ -f $k ] || [ -d $k ] ; then
                # Only execute command if dir/file exist
                echo -e "\t^^^ Above file/dir exist! ^^^"
            fi
        done
    done
done

出力の説明

以下は、状況を説明するための部分的な出力です。

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store
    ^^^ Above file/dir exist! ^^^

上記は自明です。

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist

上記は、excludeエントリ($j)にワイルドカードがないために表示され、$i/$jはプレーンな文字列連結になります。ただし、ファイル/ディレクトリが存在しません。

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map

上記は、excludeエントリ($j)にワイルドカードが含まれているが、ファイル/ディレクトリの一致がない場合に表示され、$i/$jのグロビングは元の文字列を返すだけです。

V2

V2では、一重引用符evalおよびshopt -s nullglobを使用して、きれいな結果を取得しています。ファイル/ディレクトリの最終チェックは必要ありません。

#!/bin/bash
exclude='
*.launch
.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='
$HOME/code/private/*
$HOME/code/public/*
'

for i in $dirs; do
    for j in $exclude ; do
        shopt -s nullglob
        eval "k=$i/$j"
        for l in $k; do
            echo $l
        done
        shopt -u nullglob
    done
done
2
John Siu