Как правильно экранировать одинарные кавычки в именах файлов?

У меня есть файлы с одинарными кавычками в имени, скажем 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, но пока ничего не помогло.

Также есть файлы со знаком $, поэтому лучшим решением будет очистить имена для большинства худших ситуаций, которые, конечно, могут возникнуть!

@jonrsharpe, почему удалили awk из заголовка? этот вопрос на самом деле касается некоторых тонкостей (g)awk.

jgran 10.07.2024 18:48

Можете ли вы показать результат fd .

anubhava 10.07.2024 18:55
stackoverflow.com/help/tagged
jonrsharpe 10.07.2024 18:57

@anubhava fd . производит o'a. Я использую дополнительные параметры, например fd . -tf -a, чтобы получить полный путь и только файлы, но дело не в этом!

jgran 10.07.2024 19:11

Как и в GNU find, fd имеет возможность -0 однозначно разделять имена файлов с произвольными символами в именах. К сожалению, Awk на самом деле не может справиться с вводом, завершающимся нулем, хотя GNU Awk предположительно с этим справится. Возможно, см. также mywiki.wooledge.org/BashFAQ/020

tripleee 10.07.2024 19:11

Ваш случай, вероятно, имел бы больше смысла, если бы fd . -x stat имел несколько разумных вариантов украшения вывода. Сложность заключается в том, что вызов другой команды из Awk на самом деле не позволяет цитировать произвольные символы кавычек и т. д.

tripleee 10.07.2024 19:11

@triplee, спасибо, но я предвидел это замечание и написал: «Пожалуйста, не рассказывайте мне об опции fd -x», поскольку у меня есть несколько команд, и меня интересует решение awk, которое озадачивало меня весь день ;-) !

jgran 10.07.2024 19:13

Почему Awk является обязательным требованием? Это было бы довольно легко с Perl или Python.

tripleee 10.07.2024 19:14

@triplee, почему бы не поглазеть? Я настроил это так и подумал, что это будет просто :-) Я все равно не использую Perl.

jgran 10.07.2024 19:17

Если вы не хотите использовать fd -x, потому что ваша «команда — это фрагмент более крупной команды», понимаете ли вы, что sh -c 'CMD' _ {} — это также команда, которую вы можете передать fd -x, и что CMD может быть чем угодно, от простых команд (stat ...) до вызов функции оболочки (foobar) или сценарий оболочки (/opt/super-complicated) или что-нибудь еще, что может выполнить оболочка? (замените sh на bash, python или что-нибудь еще, если хотите).

Renaud Pacalet 11.07.2024 14:54

@RenaudPacalet Спасибо за ответ на мой вопрос. Нет, я действительно не знал! Я все еще борюсь с именами файлов, содержащими одинарные кавычки и знак доллара, а иногда и то и другое (нет другого выбора, кроме как оставить их как есть). Разве команда не должна быть fd -x sh -c 'CMD {}'? Я не понимаю вашего sh -c 'CMD' _ {} (который все равно не будет работать с дурацкими именами файлов, верно?).

jgran 11.07.2024 15:26

Он должен работать с любым именем файла, с любым символом в нем (кавычки, знаки доллара, символы новой строки...). У меня здесь нет fd, но вы можете попробовать fd . -x sh -c 'printf "name: %s\n" "$1"; stat -c "%U" "$1"' _ {} \; для примера обработки, который вы упомянули в своем вопросе. Обратите внимание, что решения на основе awk могут потерпеть неудачу, если имена ваших файлов содержат символы новой строки. Таким образом, они не только более сложны и менее элегантны, но и, возможно, менее надежны.

Renaud Pacalet 11.07.2024 15:40

Я добавил ответ с некоторыми пояснениями.

Renaud Pacalet 11.07.2024 16:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
13
158
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Оболочка не интерпретирует содержимое строк в одинарных кавычках.

Единственный недопустимый символ в строке с одинарными кавычками — это одинарные кавычки.

Отдельные одинарные кавычки можно безопасно передавать в оболочку, если они экранируются обратной косой чертой.

Так:

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) ...'

jhnc 10.07.2024 20:21

Спасибо. Я бы не смог найти это решение сам! есть awk-изм, который я узнал позже, пробелы перед s в enquote(unsafe, s) добавлены для удобства чтения, если не ошибаюсь, &, о котором я не знал в gsub. И меня уже укусил \', который, как ни удивительно, вообще не исчез в цитируемом awk теле. Awk — сложная штука для новичков 😉!

jgran 11.07.2024 09:56

@jgran рад, что это было полезно. Я был бы удивлен, если бы этот вопрос не был дубликатом, хотя я не могу его найти, но я только что нашел unix.stackexchange.com/a/64218/333919

jhnc 11.07.2024 10:38

да, пробелы — это соглашение, помогающее читателю отличить ожидаемые параметры от тех, которые используются в качестве локальных переменных. Я не думаю, что awk это волнует. например: awk 'function f(a,b){printf "(%s)(%s)\n",a,b} BEGIN{f(1);f(1,2)}'

jhnc 11.07.2024 10:49

Обратите внимание, что это не удастся, если имена файлов содержат символы новой строки.

Renaud Pacalet 11.07.2024 16:36

@RenaudPacalet да, я бы предложил что-то вроде вашего ответа, если бы в вопросе мне прямо не было сказано не делать этого :-/

jhnc 11.07.2024 17:09

#jhnc Да, я думаю, это была проблема XY.

Renaud Pacalet 11.07.2024 17:14

@jhnc да, извини, мой вопрос был на самом деле awk вопросом, чтобы немного продвинуться в этом вопросе. Я не хотел, чтобы меня перенаправили на другое решение и остался со своим вопросом awk, который меня удивил. Я не знал, как лучше сформулировать свой вопрос, поскольку кураторы SOF иногда быстро голосуют против! Все были очень милы и очень полезны. Очень хороший опыт SOF!

jgran 12.07.2024 15:10

и в качестве побочного эффекта я узнал о проблеме X-Y.

jgran 12.07.2024 15:30

Как и в 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 вызывает команду несколько раз с пакетами файлов». Это верно. Но:

  1. fd запускает команды параллельно (в пределах количества ядер ЦП или NJOBS, если используется опция -j NJOBS).
  2. fd также есть опция -X (см. ниже), позволяющая передать команде сразу несколько найденных файлов.
  3. Решения, основанные на 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"' _ {}`?

jgran 11.07.2024 16:20

Пожалуйста. Нет, я не читал у вас в голове, но ваш вопрос имел привкус «проблемы X-Y», так что... Ибо fd -x, man fd говорит вам все. А man sh (или man bash, man dash) расскажет вам все о sh -c. Единственный трюк с функцией bash заключается в том, что ее необходимо экспортировать (export -f foo) так, чтобы она была передана в среду дочерних процессов.

Renaud Pacalet 11.07.2024 16:24

В основном я работаю в Windows, но использую множество портативных инструментов Rust. К сожалению, у меня нет рефлекса использовать человека. Я прочитал https://github.com/sharkdp/fd и не увидел упомянутого синтаксиса _ {}.

jgran 11.07.2024 16:48

Ой, извини, я вижу. В руководстве fd вы узнаете об опции {} (в разделе о -x). Но не про _ {}, потому что это _, как я объясняю, всего лишь артефакт. Нам нужно что-то для command_name в команде sh -c .... Так почему бы и нет _? При желании вы можете заменить на my_nice_script.

Renaud Pacalet 11.07.2024 17:12

Еще раз спасибо. Теперь это совершенно ясно. И, кроме того, ваше решение, использующее только fd . -x bash -c 'foo "$1"' _ {}, является самым простым и быстрым. По пути я также узнал немного больше о awk, что может оказаться полезным. Я бы принял этот ответ как решение, но я построил свой вопрос вокруг awk, поэтому ответ немного не по теме по сравнению с вопросом, хотя и очень актуален.👍

jgran 11.07.2024 17:36

Приятно знать, что вы узнали что-то полезное. И вы правы, что не принимаете этот ответ, это не ответ на ваш первоначальный вопрос о awk и цитировании. Принятие этого озадачит будущих читателей. Тот, который вы приняли, идеален, мой — всего лишь дополнение.

Renaud Pacalet 11.07.2024 17:53

На самом деле есть разница, которая в некоторых случаях может быть существенной: fd|cmd объединяет все файлы в одну команду. fd -x cmd вызывает команду несколько раз с пакетами файлов

jhnc 11.07.2024 17:53

@jhnc Если вы имеете в виду, что fd -x cmd запускает столько команд, сколько найдено файлов, вы правы. Но 1) fd запускает команды параллельно (в зависимости от количества ядер ЦП или опции -j), 2) fd также есть опция -X (я добавлю это в свой ответ) и 3) решения на основе awk также появляются одна оболочка на входную строку. С точки зрения производительности fd -x должен быть лучше, чем awk в большинстве, если не во всех случаях.

Renaud Pacalet 11.07.2024 18:00

На самом деле я путал с find (я не использовал fd), но принцип тот же - все: find / | wc -l против партии: find / -exec sh -c 'echo $#' - {} +

jhnc 11.07.2024 18:24

разница может быть важной, если требуются глобальные знания (возможно, команда — это сценарий awk, который создает таблицу поиска из файлов)

jhnc 11.07.2024 18:29

А, понятно, извини, я неправильно понял твой комментарий. Опция -x в fd не имеет варианта +, как опция -exec в find. Вместо этого у него есть опция -X, которая принимает несколько имен файлов одновременно. И да, если нужны глобальные знания трубопровода, было бы лучше. Но это не то, что мы можем сделать из ФП. Кроме того, имена файлов со встроенными символами новой строки могут стать проблемой.

Renaud Pacalet 11.07.2024 18:29

@RenaudPacalet: мне любопытно, кто или что в настоящее время на самом деле создает имена файлов с переводами строк, а не программное обеспечение, которое является враждебным и гнусным по своей конструкции, например программы-вымогатели.

RARE Kpop Manifesto 17.07.2024 14:54

@RARKpopManifesto Интересный исследовательский проект. У меня нет на это бюджета, но мне тоже было бы интересно. графический интерфейс? Плохо спроектированные сценарии? Люди, которые также добавляют в имена файлов пробелы, табуляции и т. д. и не понимают, почему им этого не следует делать? Люди, которые делают именно то, что предлагает ChatGPT? Рептилоиды?

Renaud Pacalet 17.07.2024 17:07

@RenaudPacalet: вот почему я специально сказал только «переносы строк». Пробелы в именах файлов вполне разумны, табуляции несколько маргинальны, но символы новой строки — это действительно сумасшедшая связка.

RARE Kpop Manifesto 19.07.2024 23:46

@RenaudPacalet: что касается «плохо спроектированных сценариев» - если бы сценарии были настолько бездушно спроектированы с самого начала, то, скорее всего, другие сценарии, расположенные дальше по цепочке выполнения, вероятно, также неправильно обрабатывали бы символы новой строки в именах файлов. Если бы исторические несправедливости всегда оставались в силе, «потому что так всегда делалось», то рабство никогда бы не было отменено.

RARE Kpop Manifesto 19.07.2024 23:53

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

Renaud Pacalet 20.07.2024 07:55

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

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 мог сделать с любым другим вводом, задайте новый вопрос, если вам нужна помощь с этим.

Другие вопросы по теме