Как ввести имя файла в сценарий оболочки в сценарии Ruby

Сценарий Ruby, в котором я хочу запустить сценарий оболочки и передать имя файла с пробелами. Я испробовал дюжину способов сделать это. Вот один:

filename = "/Users/gscar/Pictures/_Photo Processing Folders/Watched folder for import to Photos/2024.03.25-16.09.59.gs.O.orf"
filename = filename.gsub(' ', '\ ')
'exiftool -Camera:DriveMode #{filename}'

Другое включает жесткое кодирование пути.

result = %x{exiftool -Camera:DriveMode /Users/gscar/Pictures/_Photo\ Processing\ Folders/Watched\ folder\ for\ import\ to\ Photos/2024.03.25-16.09.59.gs.O.orf}

что приводит к

8 файлов не удалось прочитать

и

system 'exiftool -Camera:DriveMode "/Users/gscar/Pictures/_Photo Processing Folders/Watched folder for import to Photos/2024.03.25-16.09.59.gs.O.orf"'

без вывода.

Командная строка

➜ exiftool -Camera:DriveMode "/Users/gscar/Pictures/_Photo Processing Folders/Watched folder for import to Photos/2024.03.25-16.09.59.gs.O.orf"
Drive Mode                      : Single Shot; Electronic shutter

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

➜ exiftool -Camera:DriveMode /Users/gscar/Pictures/_Photo\ Processing\ Folders/Watched\ folder\ for\ import\ to\ Photos/2024.03.25-16.09.59.gs.O.orf
Drive Mode                      : Single Shot; Electronic shutter

Что мне нужно в конечном итоге, так это чтобы что-то вроде этого работало. Вышеупомянутое пытается выяснить, как заставить имя файла работать:

filename = "/Users/gscar/Pictures/_Photo Processing Folders/Watched folder for import to Photos/2024.03.25-16.09.59.gs.O.orf"
gsubfilename = filename.gsub(' ', '\ ')
`"exiftool -Camera:DriveMode #{gsubfilename}"`

и результат:

sh: exiftool -Camera:DriveMode /Users/gscar/Pictures/_Photo\ Processing\ Folders/Watched\ folder\ for\ import\ to\ Photos/2024.03.25-16.09.59.gs.O.orf: No such file or directory

но, конечно, запуск этого в оболочке работает нормально.

Что здесь «ускользает» от меня? Спасибо

PS mini_exiftool, похоже, не поддерживает DriveMode.

Рассматривали ли вы вместо этого использование Open3 ? Или хотя бы многоаргументную форму системы? Если вы не используете оболочку, вам не нужно беспокоиться об экранировании чего-либо из оболочки, и нет смысла вызывать оболочку для запуска чего-либо за вас, если вы можете запустить ее напрямую, без оболочки.

mu is too short 01.04.2024 23:24

Проблема, с которой вы столкнулись, связана с тем, как Ruby обрабатывает обратную косую черту в строках. Когда вы используете gsub для замены пробелов на \, Ruby интерпретирует обратную косую черту как escape-символ, а не буквальную обратную косую черту. Чтобы это исправить, вы можете использовать двойную обратную косую черту (\\) в вызове gsub.

Rajagopalan 02.04.2024 01:24

@mu слишком короткий. Я не совсем понимаю, о чем вы говорите. Open3 или system не работает в оболочке? В любом случае я попробовал это: помещает "\n#{LINE}. Демо-версия Open3, чтобы убедиться, что Open3 доступен, и это было" stdout_s, status = Open3.capture2('echo "Foo"') помещает "\n#{LINE}. Open3 в действии для имени файла" stdout_s, status = Open3.capture2(exiftool -Camera:DriveMode "#{filename}") помещает "stdout_s: #{stdout_s}status: #{status}" Результат: 34. Open3 в действии для имени файла sh: Диск: команда не найдена sh: Электронный : команда не найдена stdout_s: статус: pid 7652 выход 127

Greg 02.04.2024 07:22

Не создавайте командную строку как строку. Например: system('exiftool', '-Camera:DriveMode', filename). Подобные подходы работают и с Open3. Если вы сделаете это таким образом, вы не запустите оболочку и вам не придется иметь дело с проблемами цитирования и экранирования оболочки, см. документацию, на которую я ссылался выше.

mu is too short 02.04.2024 07:44

@muistooshort Я согласен, но придирка: реальных проблем с цитированием и экранированием оболочки нет, если вы не пытаетесь сделать это самостоятельно. Это решаемая проблема. Точно так же, как вам не следует пытаться писать CSV без помощи модуля CSV, вам не следует обращаться к MySQL без гема адаптера MySQL, точно так же вы не должны использовать gsub для экранирования аргументов оболочки, а лучше делегировать задачу соответствующему пользователю. библиотека. Единственная проблема заключается в том, что, как и в случае с SQL, если вы полагаетесь на экранирование, вы можете забыть об этом и, таким образом, создать ошибку/уязвимость. Это делает no-shell более защищенным от ошибок программиста.

Amadan 02.04.2024 09:38

ОП: Обратный cmd — почти синоним system("cmd"). Размещая обратные кавычки внутри Open3.capture2, вы сначала выполняли exiftool, а затем передавали выходные данные exiftool в качестве команды для выполнения Open3.capture2. Вот почему вы получили ошибку: вывод exiftool не является строкой, описывающей исполняемую команду, и когда вы пытаетесь запустить ее как одну, вы получаете мусор.

Amadan 02.04.2024 09:41

@Amadan Не видел проблемы с обратным кавычком в примере OP Open3.capture2, потому что они не были экранированы из Markdown. Говоря о побеге от проблем...

mu is too short 02.04.2024 16:40

@muistooshort :D Вы совершенно правы. На самом деле я до сих пор не знаю, как правильно сделать уценку «обратная галочка - cmd - обратная галочка».

Amadan 03.04.2024 03:46
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
0
8
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Как подсказывают комментарии, лучший способ — избегать оболочки и вызывать команду с аргументами в массиве. В частности, для system, если вы передадите функции хотя бы один аргумент, помимо имени программы, оболочка не будет вызываться.

system('exiftool', '-Camera:DriveMode', filename)

Таким образом, Ruby напрямую вызывает exiftool и передает ему заданные аргументы. Поскольку оболочка не задействована, вы не можете расширять параметры (например, system('ls', '*') будет жаловаться, что нет файлов с именем *).


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

require 'Shellwords'

system("exiftool -Camera:DriveMode #{filename.shellescape}")

Вызванный таким образом, system вызовет $SHELL (например, /bin/sh) и попросит его выполнить переданную строку. Затем оболочка будет обрабатывать строку, как обычно, разделяя ее на команду и аргументы, выполняя подстановку параметров и все остальное, что оболочка делает перед выполнением команды. Таким образом, system('ls *') делает то, что вы ожидаете: выводит список файлов в текущем каталоге (потому что перед вызовом * оболочка расширила бы ls на несколько аргументов).

Спасибо вам обоим, @mu слишком коротко @Amadan. Подведу итог для себя: system('exiftool', '-Camera:DriveMode', filename). Не запускает оболочку, и вам не придется иметь дело с проблемами цитирования и экранирования оболочки. Если вы передадите функции хотя бы один аргумент, помимо имени программы, оболочка не будет вызвана. Хотя я должен признать, что не понимаю, почему это позволяет избежать проблем с экранированием оболочки, но позволяет пройти через оболочку, подоболочку, командную_строку и т. д. Другими словами, многие слова я не полностью понимаю. система, Ruby-doc.org/3.3.0/Kernel.html#method-i-system

Greg 02.04.2024 18:03

Если оболочка не вызывается, параметры передаются напрямую. Например, пространство — это просто пространство. 'foo bar' — это единственный аргумент. Если вызывается оболочка, она отвечает за синтаксический анализ; 'foo bar' — это два аргумента, для разделения аргументов используется пробел, и если вы хотите включить пробел внутри аргумента, вам нужно либо использовать дополнительные кавычки ('"foo bar"'), либо экранировать аргумент ('foo\\ bar'). shellescape делает это за вас.

Amadan 03.04.2024 03:48

Чтобы завершить ответы от @mu, слишком коротко, а @Amadan

driveMode = system('exiftool', '-Camera:DriveMode', fn) 

возвращает true или false, который описан в [system][1], а это не то, что я искал.

Таким образом, полный ответ должен включать в себя то, как получить ценность. Длинное обсуждение и предложения [здесь][2]. Это сработало для меня:

      r, w = IO.pipe
      system('exiftool', '-Camera:DriveMode', fn, out: w)
      w.close
      driveMode = r.read


  [1]: https://ruby-doc.org/current/Kernel.html#method-i-system
  [2]: https://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby

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