Как использовать popen с аргументами командной строки содержит одинарные кавычки и двойные кавычки? - PullRequest
2 голосов
/ 13 апреля 2020

Я хочу выполнить следующую команду jq с subprocess.Popen() в python3.

$ jq  'INDEX(.images[]; .id) as $imgs | {
    "filename_with_label":[
         .annotations[]
        | select(.attributes.type=="letter" )
        | $imgs[.image_id] + {label:.text}
        | {id:.id} + {filename:.file_name} + {label:.label}
     ]
   }' image_data_annotation.json > image_data_annotation_with_label.json

Обратите внимание, что первый аргумент командной строки содержит точку, знак доллара, двойные кавычки в одинарных кавычках. К вашему сведению, jq - это утилита процессора JSON для обработки json файлов.

Я написал следующий python3 скрипт для автоматизации обработки файлов JSON с помощью утилиты jq.

#!python3
# file name: letter_image_tool.py

import os, subprocess

"""
command line example to automate
$ jq  'INDEX(.images[]; .id) as $imgs | {
    "filename_with_label":[
         .annotations[]
        | select(.attributes.type=="letter" )
        | $imgs[.image_id] + {label:.text}
        | {id:.id} + {filename:.file_name} + {label:.label}
     ]
   }' image_data_annotation.json > image_data_annotation_with_label.json
"""

# define first command line argument
jq_filter='\'INDEX(.images[]; .id) as $imgs | { "filename_with_label" : [ .annotations[] | select(.attributes.type=="letter" ) | $imgs[.image_id] + {label:.text} | {id:.id} + {filename:.file_name} + {label:.label} ] }\''

input_json_files= [ "image_data_annotation.json"]
output_json_files= []

for input_json in input_json_files:
    print("Processing %s" %(input_json))
    filename, ext = os.path.splitext(input_json)
    output_json = filename + "_with_label" + ext
    output_json_files.append(output_json)
    print("output file is : %s" %(output_json))

    #jq_command ='jq' + " " +  jq_filter, input_json + ' > ' +  output_json
    jq_command =['jq', jq_filter,  input_json + ' > ' +  output_json]
    print(jq_command)
    subprocess.Popen(jq_command, shell=True)

Запуск приведенного выше скрипта python на bash приводит к следующему:

$ ./letter_image_tool.py
Processing image_data_annotation.json
output file is : image_data_annotation_with_label.json
['jq', '\'INDEX(.images[]; .id) as $imgs | { "filename_with_label" : [ .annotations[] | select(.attributes.type=="letter" ) | $imgs[.image_id] + {label:.text} | {id:.id} + {filename:.file_name} + {label:.label} ] }\'', 'image_data_annotation.json > image_data_annotation_with_label.json']
jq - commandline JSON processor [version 1.6-124-gccc79e5-dirty]

Usage:  jq [options] <jq filter> [file...]
        jq [options] --args <jq filter> [strings...]
        jq [options] --jsonargs <jq filter> [JSON_TEXTS...]

jq is a tool for processing JSON inputs, applying the given filter to
its JSON text inputs and producing the filter's results as JSON on
standard output.

The simplest filter is ., which copies jq's input to its output
unmodified (except for formatting, but note that IEEE754 is used
for number representation internally, with all that that implies).

For more advanced filters see the jq(1) manpage ("man jq")
and/or https://stedolan.github.io/jq

Example:

        $ echo '{"foo": 0}' | jq .
        {
                "foo": 0
        }

For a listing of options, use jq --help.

Он не обрабатывает первый аргумент утилиты jq:

'INDEX(.images[]; .id) as $imgs | {
    "filename_with_label":[
         .annotations[]
        | select(.attributes.type=="letter" )
        | $imgs[.image_id] + {label:.text}
        | {id:.id} + {filename:.file_name} + {label:.label}
     ]
   }'

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

Я думаю, что основные проблемы связаны с точкой, знаком доллара, одинарной и двойной кавычкой, которые используются в первом аргументе командной строки (jq_filter в приведенном выше сценарии python). Но я не знаю, как относиться к этому сложному метасимволу, связанному с bash.

Что мне делать, чтобы решить вышеуказанные проблемы?

Спасибо за ваше любезное чтение.

Обновление с моим решением

С тройной кавычкой для определения jq_filter и разделенным пробелами соединением следующим образом

#!python3
# file name: letter_image_tool.py

import os, subprocess

"""
command line example to automate
$ jq  'INDEX(.images[]; .id) as $imgs | {
    "filename_with_label":[
         .annotations[]
        | select(.attributes.type=="letter" )
        | $imgs[.image_id] + {label:.text}
        | {id:.id} + {filename:.file_name} + {label:.label}
     ]
   }' image_data_annotation.json > image_data_annotation_with_label.json
"""

# define first command line argument with triple quotes
jq_filter=""" 'INDEX(.images[]; .id) as $imgs | { 
       "filename_with_label" : [ 
        .annotations[] 
       | select(.attributes.type=="letter" ) 
       | $imgs[.image_id] + {label:.text} 
       | {id:.id} + {filename:.file_name} + {label:.label} ] } ' """

input_json_files= [ "image_data_annotation.json"]
output_json_files= []

for input_json in input_json_files:
    print("Processing %s" %(input_json))
    filename, ext = os.path.splitext(input_json)
    output_json = filename + "_with_label" + ext
    output_json_files.append(output_json)
    print("output file is : %s" %(output_json))

    #jq_command ='jq' + " " +  jq_filter, input_json + ' > ' +  output_json
    # jq command composed with space separated join
    jq_command =' '.join['jq', jq_filter,  input_json, ' > ',  output_json]
    print(jq_command)

    # shell keyword argument should be set True
    subprocess.Popen(jq_command, shell=True)

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

1 Ответ

1 голос
/ 14 апреля 2020

Причина, по которой вам нужны одинарные кавычки, состоит в том, чтобы не допустить, чтобы оболочка произвела расширение вашего аргумента. Это проблема, только при использовании shell=True. Если это не установлено, оболочка никогда не будет касаться ваших аргументов, и нет необходимости «защищать» их.

Однако оболочка также отвечает за перенаправление stdout (то есть [... '>', output_json]). Не используя оболочку, требует, чтобы перенаправление обрабатывалось в коде Python. Однако это так же просто, как добавление аргумента stdout=... к Popen.

Все это означает, что ваш код можно переписать как

import os
import subprocess

# Still define first command line argument with triple quotes for readability
# Note that there are no single quotes though
jq_filter = """INDEX(.images[]; .id) as $imgs | {
       "filename_with_label" : [
        .annotations[]
       | select(.attributes.type=="letter" )
       | $imgs[.image_id] + {label:.text}
       | {id:.id} + {filename:.file_name} + {label:.label} ] }"""

input_json_files = ["image_data_annotation.json"]
output_json_files = []

for input_json in input_json_files:
    print("Processing %s" % (input_json))
    filename, ext = os.path.splitext(input_json)
    output_json = filename + "_with_label" + ext
    output_json_files.append(output_json)
    print("output file is : %s" % (output_json))

    # Keep command as list, since this is what we need when NOT using shell=True
    # Note also that the redirect and the output file are not parts of the argument list
    jq_command = ['jq', jq_filter,  input_json]

    # shell keyword argument should NOT be set True
    # Instead redirect stdout to an out_file
    # (We must open the file for writing before redirecting)
    with open(output_json, "w") as out_file:
        subprocess.Popen(jq_command, stdout=out_file)

Как правило, это в любом случае рекомендуется не использовать shell=True, поскольку это открывает еще один вектор атаки на код, поскольку атака с помощью инъекций может дать полный доступ к оболочке. Кроме того, еще одно небольшое преимущество, связанное с отсутствием использования оболочки, заключается в том, что она уменьшает количество созданных подпроцессов, поскольку не требуется никакого дополнительного процесса оболочки.

...