Как передать подстановочный знак «*» в параметр пути команды find через переменную в скрипте?

Как передать подстановочный знак «*» в параметр пути команды find через переменную в скрипте?

Я хочу использовать его findдля поиска файлов в наборе папок, ограниченных подстановочными знаками, но где в имени пути есть пробелы.

Из командной строки это просто. Все следующие примеры работают.

find  te*/my\ files/more   -print
find  te*/'my files'/more  -print
find  te*/my' 'files/more  -print

Они найдут файлы, например, в форматах terminal/my files/moreи tepid/my files/more​​.

Однако мне нужно, чтобы это было частью сценария; мне нужно что-то вроде этого:

SEARCH='te*/my\ files/more'
find ${SEARCH} -print

К сожалению, что бы я ни делал, похоже, я не могу смешивать подстановочные знаки и пробелы в findкоманде внутри скрипта. Приведенный выше пример возвращает следующие ошибки (обратите внимание на неожиданное удвоение обратной косой черты):

find: ‘te*/my\\’: No such file or directory
find: ‘files/more’: No such file or directory

Попытка использовать кавычки также не увенчалась успехом.

SEARCH="te*/'my files'/more"
find ${SEARCH} -print

Это возвращает следующие ошибки, игнорируя значение кавычек:

find: ‘te*/'my’: No such file or directory
find: ‘files'/more’: No such file or directory

Вот еще один пример.

SEARCH='te*/my files/more'
find ${SEARCH} -print

Как и ожидалось:

find: ‘te*/my’: No such file or directory
find: ‘files/more’: No such file or directory

Все варианты, которые я пробовал, возвращали ошибку.

У меня есть обходной путь, который потенциально опасен, поскольку возвращает слишком много папок. Я преобразую все пробелы в вопросительный знак (односимвольный подстановочный знак) следующим образом:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /?}       # Convert every space to a question mark.
find ${SEARCH} -print

Это эквивалентно:

find te*/my?files/more -print

Это возвращает не только правильные папки, но и terse/myxfiles/more, что не должно быть так.

Как мне добиться того, чего я пытаюсь добиться? Google мне не помог :(

решение1

Точно такая же команда должна работать в скрипте:

#!/usr/bin/env bash
find  te*/my\ files/ -print

Если вынуждатьсяесли использовать его как переменную, то все становится немного сложнее:

#!/usr/bin/env bash
search='te*/my\ files/'
eval find "$search" -print

ПРЕДУПРЕЖДЕНИЕ:

Использование evalподобных конструкций небезопасно и может привести к выполнению произвольного и потенциально вредоносного кода, если имена ваших файлов могут содержать определенные символы. См.Часто задаваемые вопросы по bash 48для получения подробной информации.

Лучше передать путь в качестве аргумента:

#!/usr/bin/env bash
find "$@" -name "file*"

Другой подход — полностью избегать findи использовать расширенные возможности подстановки bash и подстановки:

#!/usr/bin/env bash
shopt -s globstar
for file in te*/my\ files/**; do echo "$file"; done

Опция globstarbash позволяет использовать **для рекурсивного сопоставления:

globstar
      If set, the pattern ** used in a pathname expansion con‐
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

Чтобы он действовал на 100% как поиск и включение dotfiles (скрытых файлов), используйте

#!/usr/bin/env bash
shopt -s globstar
shopt -s dotglob
for file in te*/my\ files/**; do echo "$file"; done

Вы можете выровнять echoих напрямую, без цикла:

echo te*/my\ files/**

решение2

А как насчет массивов?

$ tree Desktop/ Documents/
Desktop/
└── my folder
    └── more
        └── file
Documents/
└── my folder
    ├── folder
    └── more

5 directories, 1 file
$ SEARCH=(D*/my\ folder)
$ find "${SEARCH[@]}" 
Desktop/my folder
Desktop/my folder/more
Desktop/my folder/more/file
Documents/my folder
Documents/my folder/more
Documents/my folder/folder

(*)расширяется в массив всего, что соответствует подстановочному знаку. И "${SEARCH[@]}"расширяется во все элементы массива ( [@]), каждый из которых заключен в кавычки.

С опозданием я понимаю, что find сам должен быть способен на это. Что-то вроде:

find . -path 'D*/my folder/more/'

решение3

Сейчас это немного устарело, но, если это может кому-то помочь с этим вопросом, использование символа сортировки RE [[.space.]]без заключения $SEARCHпеременной в кавычки в аргументах командной строки find работает, пока в расширенных именах путей вместо звездочки нет специальных символов.

set -x
mkdir -p te{flon,nnis,rrine}/my\ files/more
SEARCH=te*/my[[.space.]]files/more
find $SEARCH

дают следующие результаты:

+ mkdir -p 'teflon/my files/more' 'tennis/my files/more' 'terrine/my files/more'
+ SEARCH='te*/my[[.space.]]files/more'
+ find 'teflon/my files/more' 'tennis/my files/more' 'terrine/my files/more'
teflon/my files/more
tennis/my files/more
terrine/my files/more

Чтобы предотвратить глобализацию нежелательных символов, можно заменить звездочку ( *) любыми элементами сортировки (символами):

set -x
mkdir -p -- te{,-,_}{flon,nnis,rrine}/my\ files/more
SEARCH=te[[:alnum:][.space.][.hyphen.][.underscore.]]*/my[[.space.]]files/more
find $SEARCH

давая следующие результаты:

+ mkdir -p -- 'teflon/my files/more' 'tennis/my files/more' 'terrine/my files/more' \
'te-flon/my files/more' 'te-nnis/my files/more' 'te-rrine/my files/more' \
'te_flon/my files/more' 'te_nnis/my files/more' 'te_rrine/my files/more'
+ SEARCH='te[[:alnum:][.space.][.hyphen.][.underscore.]]*/my[[.space.]]files/more'
+ find 'te-flon/my files/more' 'te_flon/my files/more' 'teflon/my files/more' \
'te-nnis/my files/more' 'te_nnis/my files/more' 'tennis/my files/more' \
'te-rrine/my files/more' 'te_rrine/my files/more' 'terrine/my files/more'
te-flon/my files/more
te_flon/my files/more
teflon/my files/more
te-nnis/my files/more
te_nnis/my files/more
tennis/my files/more
te-rrine/my files/more
te_rrine/my files/more
terrine/my files/more

Обратите внимание, что для сокращения строки [.hyphen.]можно заменить на или [.-.], - а [.underscore.]можно заменить на [._.]или _.



Обрезанные строки с обратными косыми чертами ( \) добавлены мной для удобства чтения.

решение4

Я наконец нашел ответ.

Добавьте обратную косую черту ко всем пробелам:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /\\ }

На данный момент SEARCHсодержит te*/my\ files/more.

Затем используйте eval.

eval find ${SEARCH} -print

Это так просто! Использование evalобходит интерпретацию, которая ${SEARCH}исходит от переменной.

Связанный контент