Сценарий 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.
Проблема, с которой вы столкнулись, связана с тем, как Ruby обрабатывает обратную косую черту в строках. Когда вы используете gsub для замены пробелов на \, Ruby интерпретирует обратную косую черту как escape-символ, а не буквальную обратную косую черту. Чтобы это исправить, вы можете использовать двойную обратную косую черту (\\) в вызове gsub.
@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
Не создавайте командную строку как строку. Например: system('exiftool', '-Camera:DriveMode', filename)
. Подобные подходы работают и с Open3
. Если вы сделаете это таким образом, вы не запустите оболочку и вам не придется иметь дело с проблемами цитирования и экранирования оболочки, см. документацию, на которую я ссылался выше.
@muistooshort Я согласен, но придирка: реальных проблем с цитированием и экранированием оболочки нет, если вы не пытаетесь сделать это самостоятельно. Это решаемая проблема. Точно так же, как вам не следует пытаться писать CSV без помощи модуля CSV
, вам не следует обращаться к MySQL без гема адаптера MySQL, точно так же вы не должны использовать gsub
для экранирования аргументов оболочки, а лучше делегировать задачу соответствующему пользователю. библиотека. Единственная проблема заключается в том, что, как и в случае с SQL, если вы полагаетесь на экранирование, вы можете забыть об этом и, таким образом, создать ошибку/уязвимость. Это делает no-shell более защищенным от ошибок программиста.
ОП: Обратный cmd
— почти синоним system("cmd")
. Размещая обратные кавычки внутри Open3.capture2
, вы сначала выполняли exiftool
, а затем передавали выходные данные exiftool
в качестве команды для выполнения Open3.capture2
. Вот почему вы получили ошибку: вывод exiftool
не является строкой, описывающей исполняемую команду, и когда вы пытаетесь запустить ее как одну, вы получаете мусор.
@Amadan Не видел проблемы с обратным кавычком в примере OP Open3.capture2
, потому что они не были экранированы из Markdown. Говоря о побеге от проблем...
@muistooshort :D Вы совершенно правы. На самом деле я до сих пор не знаю, как правильно сделать уценку «обратная галочка - cmd
- обратная галочка».
Как подсказывают комментарии, лучший способ — избегать оболочки и вызывать команду с аргументами в массиве. В частности, для 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
Если оболочка не вызывается, параметры передаются напрямую. Например, пространство — это просто пространство. 'foo bar'
— это единственный аргумент. Если вызывается оболочка, она отвечает за синтаксический анализ; 'foo bar'
— это два аргумента, для разделения аргументов используется пробел, и если вы хотите включить пробел внутри аргумента, вам нужно либо использовать дополнительные кавычки ('"foo bar"'
), либо экранировать аргумент ('foo\\ bar'
). shellescape
делает это за вас.
Чтобы завершить ответы от @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
Рассматривали ли вы вместо этого использование Open3 ? Или хотя бы многоаргументную форму системы? Если вы не используете оболочку, вам не нужно беспокоиться об экранировании чего-либо из оболочки, и нет смысла вызывать оболочку для запуска чего-либо за вас, если вы можете запустить ее напрямую, без оболочки.