Найти (команда bash) не работает с подпроцессом?

Я переименовал имя класса css в ряде шаблонов (python-django). Однако файлы css широко распространены в нескольких файлах в нескольких каталогах. У меня есть фрагмент python, чтобы начать переименование из корневого каталога, а затем рекурсивно переименовать все файлы css.

from os import walk, curdir
import subprocess

COMMAND = "find %s -iname *.css | xargs sed -i s/[Ff][Oo][Oo]/bar/g"
test_command = 'echo "This is just a test. DIR: %s"'

def renamer(command):
  print command  # Please ignore the print commands.
  proccess = subprocess.Popen(command.split(), stdout = subprocess.PIPE)
  op = proccess.communicate()[0]
  print op

for root, dirs, files in walk(curdir):
  if root:
    command = COMMAND % root
    renamer(command)

Не работает, выдает:

find ./cms/djangoapps/contentstore/management/commands/tests -iname *.css | xargs sed -i s/[Ee][Dd][Xx]/gurukul/g
find: paths must precede expression: |
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

find ./cms/djangoapps/contentstore/views -iname *.css | xargs sed -i s/[Ee][Dd][Xx]/gurukul/g
find: paths must precede expression: |
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

Когда я копирую и запускаю ту же команду (напечатанную выше), find не выдает ошибку, и sed либо не получает входных файлов, либо работает.

Что не так с фрагментом python?


person 0xc0de    schedule 08.11.2013    source источник
comment
Вам нужно использовать shell=True для работы каналов. См., например, этот ответ.   -  person Lukas Graf    schedule 09.11.2013
comment
В этом случае вам не нужна труба: op = subprocess.check_output(["find", root]+ r"-iname \*.css -exec sed -i s/foo/bar/gi {} +".split())   -  person jfs    schedule 09.11.2013


Ответы (2)


Вы пытаетесь запустить не одну команду, а конвейер оболочки из нескольких команд, и вы пытаетесь сделать это, не вызывая оболочку. Это не может работать. То, как вы это делаете, | — это всего лишь один из аргументов find, поэтому find сообщает вам, что не понимает этот аргумент с помощью того, что «пути должны предшествовать выражению: |» ошибка.

Вы можете исправить это, добавив shell=True к Popen.

Но лучшее решение — сделать конвейер на Python и не использовать оболочку. См. Замена старых функций на subprocess Module в документации для объяснения, но я покажу пример.

Между тем, вы никогда не должны использовать split для разделения командной строки. Лучшее решение — написать список отдельных аргументов, а не объединять их в строку, чтобы разделить их. Если вам необходимо это сделать, используйте модуль shlex; вот для чего это нужно. Но в вашем случае даже это вам не поможет, потому что вы вставляете случайные строки дословно, в которых легко могут быть пробелы или кавычки, и нет никакой возможности что-либо — shlex или что-то еще — может восстановить данные в первую очередь .

So:

pfind = Popen(['find', root, '-iname', '*.css'], stdout=PIPE)
pxargs = Popen(['xargs', 'sed', '-i', 's/[Ff][Oo][Oo]/bar/g'], 
               stdin=pfind.stdout, stdout=PIPE)
pfind.stdout.close()
output = pxargs.communicate()

Но здесь есть еще лучшее решение.

В Python есть os.walk, чтобы делать то же самое, что и find, вы можете легко имитировать xargs, но на самом деле в этом нет необходимости, и у него есть собственный модуль re, который можно использовать вместо sed. Так почему бы не использовать их?

Или, наоборот, bash намного лучше управляет и соединяет простые команды, чем Python, поэтому, если вы предпочитаете использовать find и sed вместо os.walk и re.sub, зачем вообще писать управляющий скрипт на Python?

person abarnert    schedule 08.11.2013
comment
+1 за хороший ответ для обоих, показывая правильные пути с примерами и исправляя мою попытку. Теперь я понимаю, что совпадение между find и os.walk в моих усилиях было просто жалкой рассеянностью. - person 0xc0de; 09.11.2013
comment
Одна из причин использования find и вместо os.walk заключается в том, что find намного быстрее. os.walk выполняет системный вызов для каждого файла, хотя в python 3.5+ есть новая функция, os.scandir, которая использует тот же системный вызов find и имеет производительность, более сравнимую с find. - person twneale; 23.09.2015
comment
@twneale На самом деле scandir не использует тот же системный вызов, что и find (по крайней мере, в современных системах GNU и BSD). Это будет fts. (Последний раз, когда я проверял, есть библиотека, обертывающая fts для Python, но у меня были некоторые проблемы с ней, поэтому я написал свою собственную неполную оболочку с ctypes. Возможно, сейчас есть лучшая.) Во FreeBSD fts может быть намного быстрее ; на linux (который, по крайней мере, для некоторых файловых систем, удивительно упреждающий кэш) или OS X (где некоторые ключевые оптимизации не работают на файловых системах Apple), вы, вероятно, в порядке с scandir. Но сначала убедитесь, что проблема с производительностью действительно существует… - person abarnert; 09.12.2015

Проблема в трубе. Чтобы использовать канал с модулем подпроцесса, вы должны передать shell=True.

person Dale Myers    schedule 08.11.2013
comment
ответ абарнерта намного точнее моего. Иди с этим. - person Dale Myers; 09.11.2013