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

9

Я хочу использовать, 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, что это не должно.

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

Падди Ландау
источник
@KasiyA я использую bash; Вы должны использовать что-то еще, поскольку я не видел эту конструкцию раньше. В результате SEARCH: command not foundкоманда find -printвыполняется.
Пэдди Ландау
Выстрел в темноте, но как насчет цитирования? find "${SEARCH}" -print?
Алаа Али
@AlaaAli Нет, это не работает, потому что цитирование не позволяет Bash использовать подстановочные знаки. Он будет искать путь конкретно с именем (в моем примере) te*/'my files'/more.
Пэдди Ландау

Ответы:

9

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

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

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

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

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

Такое использование evalнебезопасно и может привести к выполнению произвольного и, возможно, вредоносного кода, если имена ваших файлов могут содержать определенные символы. Смотрите bash FAQ 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%, как находить и включать точечные файлы (скрытые файлы), используйте

#!/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/**
terdon
источник
2
Я установил это как ответ из-за полезных комментариев, которые сделал Тердон (не забывая полезные комментарии от других). Я использовал возможность Bash в командной строке, чтобы передавать несколько путей к моему сценарию, а не сценарий, пытающийся разобраться в нем. Это работает хорошо.
Пэдди Ландау
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 . -path 'D*/my folder/more/'
Мур
источник
Умная идея, но, увы, она не работает. Почему? Потому что сам путь содержится в переменной; следовательно, INPUTPATH='te*/my files/moreи SEARCH=(${INPUTPATH}). Независимо от того, как я изменяю способ, которым я делаю это, я все равно получаю нефункциональный результат. Это кажется невозможным!
Пэдди Ландау
Это все верно, конечно, но ОП должен сделать это в сценарии. Это меняет дело, так как расширение подстановочных знаков становится значительно более сложным, и это не работает.
тердон
@PaddyLandau В таком случае, почему бы вам не использовать find«s -pathфильтр? Он использует подстановочные знаки и определенно не нуждается в расширении.
Муру
@muru Это интересно; Я не знал о -path. Однако за последние 10 минут я понял ответ: пользуйся eval! Кажется, это проще, чем -path.
Пэдди Ландау
2
@terdon и Муру и все: Спасибо. Я слышал, что вы все сказали, и я понял, что я должен заставить свой сценарий делать одну вещь, и позволять Bash-шпионам передавать несколько файлов или путей к сценарию. Я изменил свой сценарий таким образом. Он хорошо работает и лучше соответствует философии Linux. Еще раз спасибо!
Пэдди Ландау
0

Я наконец-то узнал ответ.

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

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

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

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

eval find ${SEARCH} -print

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

Падди Ландау
источник
@terdon Спасибо за предупреждение. Вернуться к доске для рисования!
Пэдди Ландау
Да, это удивительно сложно. Я только что обновил свой ответ другим подходом, почему бы вместо этого не использовать globbing? Если это по-прежнему не работает, я предлагаю вам опубликовать новый вопрос по Unix & Linux, объясняющий, какова ваша конечная цель и почему вам нужно иметь шаблон в качестве переменной. Этот тип вещей, скорее всего, получить лучший ответ там.
тердон