У меня есть файлы с одинарными кавычками в имени, скажем o'a
. Я хочу обработать их в awk (gawk-5.3.0):
(fd — альтернатива find
).
Пожалуйста, не рассказывайте мне об опции fd -x
, моя команда — это фрагмент более крупной команды, и меня действительно интересует решение awk.
fd . | awk '{ print "name: \047" $0 "\047"; cmd = "stat -c \"%U\" " $0 ; cmd | getline result ; print result }'
и я получаю ошибки:
name: 'o'a'
sh: -c: line 0: unexpected EOF while looking for matching `''
sh: -c: line 1: syntax error: unexpected end of file
Как правильно избежать кавычек в имени в awk? Я пробовал с gsub
, но пока ничего не помогло.
Также есть файлы со знаком $
, поэтому лучшим решением будет очистить имена для большинства худших ситуаций, которые, конечно, могут возникнуть!
Можете ли вы показать результат fd .
@anubhava fd .
производит o'a
. Я использую дополнительные параметры, например fd . -tf -a
, чтобы получить полный путь и только файлы, но дело не в этом!
Как и в GNU find
, fd
имеет возможность -0
однозначно разделять имена файлов с произвольными символами в именах. К сожалению, Awk на самом деле не может справиться с вводом, завершающимся нулем, хотя GNU Awk предположительно с этим справится. Возможно, см. также mywiki.wooledge.org/BashFAQ/020
Ваш случай, вероятно, имел бы больше смысла, если бы fd . -x stat
имел несколько разумных вариантов украшения вывода. Сложность заключается в том, что вызов другой команды из Awk на самом деле не позволяет цитировать произвольные символы кавычек и т. д.
@triplee, спасибо, но я предвидел это замечание и написал: «Пожалуйста, не рассказывайте мне об опции fd -x», поскольку у меня есть несколько команд, и меня интересует решение awk, которое озадачивало меня весь день ;-) !
Почему Awk является обязательным требованием? Это было бы довольно легко с Perl или Python.
@triplee, почему бы не поглазеть? Я настроил это так и подумал, что это будет просто :-) Я все равно не использую Perl.
Если вы не хотите использовать fd -x
, потому что ваша «команда — это фрагмент более крупной команды», понимаете ли вы, что sh -c 'CMD' _ {}
— это также команда, которую вы можете передать fd -x
, и что CMD
может быть чем угодно, от простых команд (stat ...
) до вызов функции оболочки (foobar
) или сценарий оболочки (/opt/super-complicated
) или что-нибудь еще, что может выполнить оболочка? (замените sh
на bash
, python
или что-нибудь еще, если хотите).
@RenaudPacalet Спасибо за ответ на мой вопрос. Нет, я действительно не знал! Я все еще борюсь с именами файлов, содержащими одинарные кавычки и знак доллара, а иногда и то и другое (нет другого выбора, кроме как оставить их как есть). Разве команда не должна быть fd -x sh -c 'CMD {}'
? Я не понимаю вашего sh -c 'CMD' _ {}
(который все равно не будет работать с дурацкими именами файлов, верно?).
Он должен работать с любым именем файла, с любым символом в нем (кавычки, знаки доллара, символы новой строки...). У меня здесь нет fd
, но вы можете попробовать fd . -x sh -c 'printf "name: %s\n" "$1"; stat -c "%U" "$1"' _ {} \;
для примера обработки, который вы упомянули в своем вопросе. Обратите внимание, что решения на основе awk
могут потерпеть неудачу, если имена ваших файлов содержат символы новой строки. Таким образом, они не только более сложны и менее элегантны, но и, возможно, менее надежны.
Я добавил ответ с некоторыми пояснениями.
Оболочка не интерпретирует содержимое строк в одинарных кавычках.
Единственный недопустимый символ в строке с одинарными кавычками — это одинарные кавычки.
Отдельные одинарные кавычки можно безопасно передавать в оболочку, если они экранируются обратной косой чертой.
Так:
fd . | awk -v q=\' '
function enquote(unsafe, s){
s = unsafe
gsub(q, "&\\\\&&", s)
return q s q
}
{
print "name: " q $0 q
cmd = "stat -c %U " enquote($0)
cmd | getline
print
close(cmd)
}
'
или может быть: ... | awk ... -v eq=\''\\'\'\' ' ... gsub(q,eq,s) ...'
Спасибо. Я бы не смог найти это решение сам! есть awk
-изм, который я узнал позже, пробелы перед s
в enquote(unsafe, s)
добавлены для удобства чтения, если не ошибаюсь, &
, о котором я не знал в gsub
. И меня уже укусил \'
, который, как ни удивительно, вообще не исчез в цитируемом awk теле. Awk — сложная штука для новичков 😉!
@jgran рад, что это было полезно. Я был бы удивлен, если бы этот вопрос не был дубликатом, хотя я не могу его найти, но я только что нашел unix.stackexchange.com/a/64218/333919
да, пробелы — это соглашение, помогающее читателю отличить ожидаемые параметры от тех, которые используются в качестве локальных переменных. Я не думаю, что awk это волнует. например: awk 'function f(a,b){printf "(%s)(%s)\n",a,b} BEGIN{f(1);f(1,2)}'
Обратите внимание, что это не удастся, если имена файлов содержат символы новой строки.
@RenaudPacalet да, я бы предложил что-то вроде вашего ответа, если бы в вопросе мне прямо не было сказано не делать этого :-/
#jhnc Да, я думаю, это была проблема XY.
@jhnc да, извини, мой вопрос был на самом деле awk
вопросом, чтобы немного продвинуться в этом вопросе. Я не хотел, чтобы меня перенаправили на другое решение и остался со своим вопросом awk, который меня удивил. Я не знал, как лучше сформулировать свой вопрос, поскольку кураторы SOF иногда быстро голосуют против! Все были очень милы и очень полезны. Очень хороший опыт SOF!
и в качестве побочного эффекта я узнал о проблеме X-Y.
Как и в GNU find
, fd
имеет возможность -0
однозначно разделять имена файлов с произвольными символами в именах. Некоторые варианты Awk не могут справиться с входными данными, завершающимися нулем, поскольку это недопустимый текстовый файл для POSIX, хотя GNU Awk и другие с этим справляются (прочитайте справочную страницу вашего awk); но затем успешная передача его в подпроцесс с правильным цитированием также становится сложной.
Однако с Perl или Python это было бы довольно легко.
fd -0 . |
perl -0 -ne '
print("name: \047$_\047\n");
system("stat", "-c", "\"%U\"", $_);'
Возможно, см. также https://mywiki.wooledge.org/BashFAQ/020
Если причина отказа от использования fd -x
заключается в том, что вы думаете, что он ограничивается простыми командами, вам, вероятно, следует пересмотреть свое решение. Команда, которую вы передаете fd -x
(или find -exec
), может быть чем угодно, например, сценарием оболочки или Python, функцией оболочки... На своем собственном примере вы можете попробовать:
fd . -x sh -c 'printf "name: \x27%s\x27\n" "$1"; stat -c "%U" "$1"' _ {}
Из руководства sh
:
sh -c command_string [command_name [argument ...]]
...
Чтение команд из операнда
command_string
вместо стандартного ввода. Специальный параметр0
будет установлен из операндаcommand_name
, а позиционные параметры ($1
,$2
и т. д.) установлены из остальных операндов-аргументов.
В нашем случае command_name
может быть чем угодно, мы не используем аргумент $0
. Я использую _
, потому что для меня это «все равно». Есть только один аргумент — заполнитель {}
, который fd
заменяет именем найденного файла. Обратите внимание, что это необязательно, поскольку fd
добавляет его по умолчанию. Я использовал это для ясности. Сценарий прост; все "$1"
заменяются первым аргументом команды. Если вы не забудете заключить его в двойные кавычки, нет необходимости обращать внимание на специальные символы, кавычки, доллары, новые строки или что-то еще, это должно работать с любым именем файла, с любым символом в нем.
Обратите внимание, что решения на основе awk
потерпят неудачу, если имена ваших файлов содержат символы новой строки. Таким образом, они не только более сложны и менее элегантны, но и менее надежны.
Конечно, если ваш сценарий более сложен, вы можете определить функцию оболочки или поместить ее в файл. Пример с bash
и функцией:
foo() {
printf "name: '%s'\n" "$1"
stat -c "%U" "$1"
}
export -f foo
fd . -x bash -c 'foo "$1"' _ {}
Редактировать: jhnc отметил в комментариях, что «fd|cmd
передает все файлы в одну команду. fd -x cmd
вызывает команду несколько раз с пакетами файлов». Это верно. Но:
fd
запускает команды параллельно (в пределах количества ядер ЦП или NJOBS
, если используется опция -j NJOBS
).fd
также есть опция -X
(см. ниже), позволяющая передать команде сразу несколько найденных файлов.awk
, также создают одну оболочку на каждую входную строку. С точки зрения производительности fd -x
должен быть лучше, чем awk
в большинстве, если не во всех случаях.Если у вас проблемы с производительностью из-за большого количества обрабатываемых файлов, вы можете поиграть с опцией -j NJOBS
или использовать опцию -X
вместо -x
, или и то, и другое:
foo() {
for f in "$@"; do
printf "name: '%s'\n" "$f"
stat -c "%U" "$f"
done
}
export -f foo
fd . -j 8 --batch-size 100 -X bash -c 'foo "$@"' _ {}
При этом будет параллельно выполняться до 8 заданий, и в каждом задании одновременно будет передаваться до 100 имен файлов foo
вместо одного с опцией -x
. Конечно, если скрипт вызывает внешние команды (например, stat
), польза будет не такой большой, поскольку для каждого файла в любом случае будет создаваться дочерний процесс.
Обратите внимание, что для поиска наилучшей комбинации -j NJOBS
(максимальное количество параллельных заданий) и --batch-size SIZE
(максимальное количество файлов на вызов команды) может потребоваться некоторое профилирование. Также обратите внимание, что существует максимальная длина команд оболочки. Если fd
не делает это автоматически (я не смог найти эту информацию в руководстве), а имена файлов длинные и/или SIZE
слишком велики, вы можете упасть в стену...
СПАСИБО большое за углубление моего вопроса и чтение в моей голове! Собственно, я почти написал ваше решение, но ошибся fd . -x bash -c 'foo {}'
. Я пытался процитировать '{}'
... Где вы нашли синтаксис `fd. -x bash -c 'foo "$1"' _ {}`?
Пожалуйста. Нет, я не читал у вас в голове, но ваш вопрос имел привкус «проблемы X-Y», так что... Ибо fd -x
, man fd
говорит вам все. А man sh
(или man bash
, man dash
) расскажет вам все о sh -c
. Единственный трюк с функцией bash
заключается в том, что ее необходимо экспортировать (export -f foo
) так, чтобы она была передана в среду дочерних процессов.
В основном я работаю в Windows, но использую множество портативных инструментов Rust. К сожалению, у меня нет рефлекса использовать человека. Я прочитал https://github.com/sharkdp/fd и не увидел упомянутого синтаксиса _ {}
.
Ой, извини, я вижу. В руководстве fd
вы узнаете об опции {}
(в разделе о -x
). Но не про _ {}
, потому что это _
, как я объясняю, всего лишь артефакт. Нам нужно что-то для command_name
в команде sh -c ...
. Так почему бы и нет _
? При желании вы можете заменить на my_nice_script
.
Еще раз спасибо. Теперь это совершенно ясно. И, кроме того, ваше решение, использующее только fd . -x bash -c 'foo "$1"' _ {}
, является самым простым и быстрым. По пути я также узнал немного больше о awk
, что может оказаться полезным. Я бы принял этот ответ как решение, но я построил свой вопрос вокруг awk
, поэтому ответ немного не по теме по сравнению с вопросом, хотя и очень актуален.👍
Приятно знать, что вы узнали что-то полезное. И вы правы, что не принимаете этот ответ, это не ответ на ваш первоначальный вопрос о awk
и цитировании. Принятие этого озадачит будущих читателей. Тот, который вы приняли, идеален, мой — всего лишь дополнение.
На самом деле есть разница, которая в некоторых случаях может быть существенной: fd|cmd
объединяет все файлы в одну команду. fd -x cmd
вызывает команду несколько раз с пакетами файлов
@jhnc Если вы имеете в виду, что fd -x cmd
запускает столько команд, сколько найдено файлов, вы правы. Но 1) fd
запускает команды параллельно (в зависимости от количества ядер ЦП или опции -j
), 2) fd
также есть опция -X
(я добавлю это в свой ответ) и 3) решения на основе awk
также появляются одна оболочка на входную строку. С точки зрения производительности fd -x
должен быть лучше, чем awk
в большинстве, если не во всех случаях.
На самом деле я путал с find
(я не использовал fd
), но принцип тот же - все: find / | wc -l
против партии: find / -exec sh -c 'echo $#' - {} +
разница может быть важной, если требуются глобальные знания (возможно, команда — это сценарий awk, который создает таблицу поиска из файлов)
А, понятно, извини, я неправильно понял твой комментарий. Опция -x
в fd
не имеет варианта +
, как опция -exec
в find
. Вместо этого у него есть опция -X
, которая принимает несколько имен файлов одновременно. И да, если нужны глобальные знания трубопровода, было бы лучше. Но это не то, что мы можем сделать из ФП. Кроме того, имена файлов со встроенными символами новой строки могут стать проблемой.
@RenaudPacalet: мне любопытно, кто или что в настоящее время на самом деле создает имена файлов с переводами строк, а не программное обеспечение, которое является враждебным и гнусным по своей конструкции, например программы-вымогатели.
@RARKpopManifesto Интересный исследовательский проект. У меня нет на это бюджета, но мне тоже было бы интересно. графический интерфейс? Плохо спроектированные сценарии? Люди, которые также добавляют в имена файлов пробелы, табуляции и т. д. и не понимают, почему им этого не следует делать? Люди, которые делают именно то, что предлагает ChatGPT? Рептилоиды?
@RenaudPacalet: вот почему я специально сказал только «переносы строк». Пробелы в именах файлов вполне разумны, табуляции несколько маргинальны, но символы новой строки — это действительно сумасшедшая связка.
@RenaudPacalet: что касается «плохо спроектированных сценариев» - если бы сценарии были настолько бездушно спроектированы с самого начала, то, скорее всего, другие сценарии, расположенные дальше по цепочке выполнения, вероятно, также неправильно обрабатывали бы символы новой строки в именах файлов. Если бы исторические несправедливости всегда оставались в силе, «потому что так всегда делалось», то рабство никогда бы не было отменено.
Как обычно, фундаментальный вопрос заключается не в том, насколько разумно или часто иметь символы новой строки в именах файлов. Вопрос в следующем: предлагаем ли мы решения, которые могут сломаться, или нет? Моя личная позиция (можете не соглашаться) заключается в том, что не зная, кто будет читать эти вопросы и ответы и что они с ними будут делать, я предпочитаю пытаться предложить решения, которые не смогут сломаться, даже с малой вероятностью. И я не говорю, что мне всегда это удается. Просто пытаюсь.
Вы получили ответ на свой вопрос, но, к вашему сведению, то, что вы делаете, очень редко бывает правильным подходом, потому что у вас есть такая иерархия вызовов:
shell {
awk {
for (each input line) {
subshell {
stat
}
}
}
}
что очень неэффективно, поскольку порождает новую подоболочку для вызова stat
один раз для каждой строки ввода. Если вместо вызова оболочки awk для вызова оболочки для вызова stat у вас просто есть вызов оболочки напрямую:
shell {
for (each input line) {
stat
}
}
тогда вы, очевидно, порождаете гораздо меньше процессов, поэтому он более эффективен и легче обрабатывать любые символы в именах файлов, потому что он использует оболочку по прямому назначению (создание/уничтожение файлов и процессов и упорядочивание вызовов инструментов) и просто использует awk по прямому назначению (манипулирование текстом), вместо того, чтобы пытаться заставить awk выполнять работу с оболочками.
Итак, если вы хотите сделать больше в awk, рассмотрите возможность сделать что-то вроде этого вместо вызова stat
из awk (используйте xargs
вместо цикла, если это удобно):
fd . |
while IFS= read -r line; do
stat -c '%U' "$line"
done |
awk 'whatever'
и существуют различные способы смешивания выходного потока вызовов stat
со всем, что вам нужно, чтобы awk мог сделать с любым другим вводом, задайте новый вопрос, если вам нужна помощь с этим.
@jonrsharpe, почему удалили awk из заголовка? этот вопрос на самом деле касается некоторых тонкостей (g)awk.