Подтвердить что ты не робот

Найти и basename не играть красиво

Я хочу выдать часть имени файла поиска в командной строке linux. Я попытался использовать следующее:

find www/*.html -type f -exec sh -c "echo $(basename {})" \;

и

find www/*.html -type f -exec sh -c "echo `basename {}`" \;

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

www/channel.html
www/definition.html
www/empty.html
www/index.html
www/privacypolicy.html

Почему бы и нет?

Обновление. Хотя у меня есть рабочее решение ниже, меня все еще интересует, почему "basename" не делает то, что он должен делать.

4b9b3361

Ответ 1

Проблема с вашей первоначальной попыткой:

find www/*.html -type f -exec sh -c "echo $(basename {})" \;

заключается в том, что код $(basename {}) выполняется один раз, перед выполнением команды find. Результатом одиночного basename является {}, так как это basename {} как имя файла. Таким образом, команда, выполняемая find:

sh -c "echo {}" 

для каждого найденного файла, но find фактически заменяет исходное (немодифицированное) имя файла каждый раз, потому что символы {} отображаются в строке, которая должна быть выполнена.

Если вы хотите, чтобы он работал, вы можете использовать одинарные кавычки вместо двойных кавычек:

find www/*.html -type f -exec sh -c 'echo $(basename {})' \;

Однако, если echo повторить стандартный вывод, который basename написал бы на стандартный вывод в любом случае, это немного бессмысленно:

find www/*.html -type f -exec sh -c 'basename {}' \;

и мы можем, тем не менее, уменьшить это:

find www/*.html -type f -exec basename {} \;

Не могли бы вы также объяснить разницу между одинарными кавычками и двойными кавычками здесь?

Это обычное поведение оболочки. Возьмем немного другую команду (но только немного - имена файлов могут быть в любом месте в каталоге www, а не только на одном уровне вниз), и посмотрите на одну кавычку (SQ) и двойную кавычку (DQ) версии команды:

find www -name '*.html' -type f -exec sh -c "echo $(basename {})" \;   # DQ
find www -name '*.html' -type f -exec sh -c 'echo $(basename {})' \;   # SQ

Одиночные кавычки передают материал, заключенный непосредственно в команду. Таким образом, в командной строке SQ оболочка, запускающая find, удаляет закрывающие кавычки, а команда find видит свой аргумент $9 как:

echo $(basename {})

потому что оболочка удаляет кавычки. Для сравнения, материал в двойных кавычках обрабатывается оболочкой. Таким образом, в командной строке DQ оболочка (которая запускает find, а не ту, которая запускается find), видит $(basename {}) часть строки и выполняет ее, возвращаясь {}, поэтому строка, которую она передает до find в качестве аргумента $9:

echo {}

Теперь, когда find выполняет свое действие -exec, в обоих случаях он заменяет {} на имя файла, которое он только что нашел (для аргумента, www/pics/index.html). Таким образом, вы выполняете две разные команды:

sh -c 'echo $(basename www/pics/index.html)'    # SQ
sh -c "echo www/pics/index.html"                # DQ

Там есть (небольшой) нотационный чит - это эквивалентные команды, которые вы вводите в оболочку. $2 запускаемой оболочки фактически не содержит кавычек в любом случае - запущенная оболочка не видит никаких кавычек.

Как вы можете видеть, команда DQ просто переименовывает имя файла; команда SQ запускает команду basename и фиксирует ее выход, а затем перехватывает захваченный вывод. Немного редукционистского мышления показывает, что команда DQ может быть записана как -print вместо использования -exec, а команда SQ может быть записана как -exec basename {} \;.

Если вы используете GNU find, он поддерживает действие -printf, за которым могут следовать Директивы формата, так что запуск basename не требуется. Однако это доступно только в GNU find; остальная часть обсуждения здесь применима к любой версии find, с которой вы, вероятно, столкнетесь.

Ответ 2

Попробуйте это вместо:

 find www/*.html -type f -printf '%f\n'

Если вы хотите сделать это с помощью трубы (требуется больше ресурсов):

find www/*.html -type f -print0 | xargs -0 -n1 basename

Ответ 3

Вот как я могу изменить размер файлов с помощью imagick, перепрошивая выходное имя файла из источника

find . -name header.png -exec sh -c 'convert -geometry 600 {} $(dirname {})/$(basename {} ".png")_mail.png' \;

Ответ 4

Мне пришлось выполнить что-то подобное, и я нашел следующие методы, упомянутые для того, чтобы избежать циклического поиска вывода и использовать find с sh, обойдя эти проблемы с {} и -printf полностью.

Вы можете попробовать это следующим образом:

find www/*.html -type f -exec sh -c 'echo $(basename $1)' find-sh {} \;

Резюме: "Не ссылайтесь {} непосредственно внутри sh -c, но вместо этого передавайте его в sh -c в качестве аргумента, тогда вы можете ссылаться на него с числовой переменной внутри sh -c" the find-sh как раз в качестве манекена для поиска $0, в этом случае есть больше полезности и с помощью {} для $1.

Я предполагаю, что использование echo действительно упрощает концепцию и функцию тестирования. Есть более простые способы просто эхо, как упомянули другие, но идеальным вариантом для этого сценария может быть использование cp, mv или любые более сложные команды, в которых вы хотите ссылаться на найденные имена файлов более одного раза в и вам нужно избавиться от пути, например. когда вам нужно указать имя файла как в источнике, так и в пункте назначения, или если вы переименовываете вещи.

Так, например, если вы хотите скопировать только html-документы в каталог public_html (почему?, потому что пример!), вы могли бы:

 find www/*.html -type f -exec sh -c 'cp /var/www/$(basename $1) /home/me/public_html/$(basename $1)' find-sh {} \;

В unix stackexchange пользовательский подстановочный ответ на цикл с поиском переходит в некоторые большие драгоценные камни при использовании -exec и sh -c. (Вы можете найти его здесь: https://unix.stackexchange.com/info/321697/why-is-looping-over-finds-output-bad-practice)