Intereting Posts
Настройка перезагрузки при панике Linux Может ли 99-рейд-чек еженедельный cronjob планировать работать с шагом? Map Control-Shift-Tab в rxvt-unicode Использовать функцию bash в конвейерной команде Можно ли запустить тонкий клиент в виртуальном боксе? Слейте ненулевые блоки огромного (разреженного) файла A в огромный файл B Как просмотреть содержимое моего жесткого диска другим пользователем, отличным от root. Я все время отказываюсь от разрешения Как я могу автоматически перезапустить панель tmux (Byobu), когда закончится ее задача? Создание электронной почты, которая может вызвать скрипт Производительность и ограничения использования X GUI удаленно? К какому файлу принадлежит / etc / inputrc? udev управляет официальными документами Как отобразить индикатор выполнения для инкрементных архивов tar? Я пытаюсь найти лучший / более простой способ подсчета алфавитных символов в переменной файла или оболочки Звуковой модуль alsa snd_aloop

Как сделать bash glob строковой переменной?

Системная информация

ОС: OS X

bash: GNU bash, версия 3.2.57 (1) -release (x86_64-apple-darwin16)

Задний план

Я хочу, чтобы машина времени исключала набор каталогов и файлов из всего моего проекта git / nodejs. Мои каталоги проектов находятся в ~/code/private/ и ~/code/public/ поэтому я пытаюсь использовать цикл bash для создания tmutil .

вопрос

Укороченная версия

Если у меня есть вычисленная строковая переменная k , как мне сделать ее glob в или прямо перед циклом 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 к ~/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 к ~/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 

Вы можете принудительно провести еще один раунд оценки с помощью eval , но на самом деле это не обязательно. (И eval начинает испытывать серьезные проблемы с момента, когда ваши имена файлов содержат специальные символы, такие как $ .) Проблема заключается не в том, что вы клонирование, а с расширением тильды.

Глобуждение происходит после изменения переменной, если переменная некорректна, как здесь (*) :

 $ 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 должен быть указан, поэтому он не случайно расширяется оболочкой.

(Я думаю, что вы можете играть с -maxdepth в GNU find, чтобы ограничить, насколько глубокий поиск идет, если вам интересно, но это немного другая проблема.)

Вы можете сохранить его как массив, а не строку, которая будет использоваться позже во многих случаях, и пусть это произойдет, когда вы определите его. В вашем случае, например:

 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 

Ответ @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 

Вышеприведенное показывает, что запись исключений ( $j ) не имеет подстановочного знака, $i/$j становится простой конкатенацией строк. Однако файл / dir не существует.

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

Вышеприведенное как исключающая запись ( $j ) содержит подстановочный знак, но не имеет соответствия файлов / каталогов, globeing из $i/$j просто возвращает исходную строку.

V2

V2 используют одинарные кавычки, eval и shopt -s nullglob чтобы получить чистый результат. Нет необходимости проверять окончание файла / dir.

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

С 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 делает зависание переменной (некоторые другие оболочки также по умолчанию (когда вы вообще этого не хотите, вы бы пришлось процитировать $f выше в других оболочках)).

(N) является квалификатором glob, который включает nullglob для каждого из этих глобусов, возникающих в результате расширения $^array1/$^array2 . Это приводит к тому, что глобусы расширяются до нуля, когда они не совпадают. Также случается превратить не-glob как ~/code/private/foo/Thumbs.db в один, что означает, что если это не существует, оно не включено.