Бесполезное использование кошки?
это, вероятно, во многих часто задаваемых вопросах-вместо использования:
cat file | command
(что называется бесполезное использование кошки), правильный способ должен быть:
command < file
во втором, "правильном" способе - ОС не должна порождать дополнительный процесс.
Несмотря на это, я продолжал использовать бесполезную кошку по двум причинам.
более эстетично-мне нравится, когда данные перемещаются равномерно только слева направо. И его легче заменить
catС чем-то еще (gzcat,echo, ...), добавить 2-й файл или вставить новый фильтр (pv,mbuffer,grep...).Я "чувствовал", что в некоторых случаях это может быть быстрее. Быстрее, потому что есть 2 процесса, 1-й (
cat) делает чтение, а второй делает все, что угодно. И они могут работать параллельно, что означает иногда более быстрое выполнение.
является ли моя логика правильной (по 2-й причине)?
8 ответов:
я не знал до сегодня, когда какой-то новичок попытался подколоть UUOC на меня за один из моих ответов. Это было
cat file.txt | grep foo | cut ... | cut .... Я дал ему часть своего ума, и только после этого посетил ссылку, которую он дал мне, ссылаясь на происхождение награды и практику этого. Дальнейшие поиски привели меня к этому вопросу. К сожалению, несмотря на сознательное рассмотрение, ни один из ответов не включал мое обоснование.я не хотел быть защищаясь в ответ на него. В конце концов, в мои молодые годы я бы написал команду
grep foo file.txt | cut ... | cut ...потому что всякий раз, когда вы делаете часто одинgreps вы изучаете размещение аргумента файла, и это готовое знание, что первый-это шаблон, а последующие-имена файлов.это был сознательный выбор, чтобы использовать
catкогда я ответил на вопрос, отчасти из-за причины "хорошего вкуса" (по словам Линуса Торвальдса), но главным образом для убедительного причина функции.последняя причина более важна, поэтому я сначала потушу ее. Когда я предлагаю трубопровод в качестве решения, я ожидаю, что он будет многоразовым. Вполне вероятно, что трубопровод будет добавлен в конце или соединен с другим трубопроводом. В этом случае наличие аргумента файла для grep завинчивает возможность повторного использования, и вполне возможно сделать это молча без сообщения об ошибке, если аргумент файл существует. То есть
grep foo xyz | grep bar xyz | wcдаст вам, сколько строк вxyzсодержатьbarв то время как вы ожидаете количество строк, которые содержат обаfooиbar. Необходимость изменения аргументов для команды в конвейере перед ее использованием может привести к ошибкам. Добавьте к этому возможность молчаливых неудач, и это станет особенно коварной практикой.первая причина тоже не маловажна, так как много "хорошего вкуса" просто является интуитивным подсознательным обоснованием таких вещей, как тихие неудачи выше, которые вы не можете думать правильно в тот момент, когда какой-то человек, нуждающийся в образовании, говорит: "но разве эта кошка не бесполезна".
тем не менее, я постараюсь также осознать прежнюю причину "хорошего вкуса", о которой я упоминал. Эта причина связана с ортогональным духом дизайна Unix.
grepнеcutиlsнеgrep. Поэтому по крайней мереgrep foo file1 file2 file3идет вразрез с духом конструкции. Ортогональный способ сделать этоcat file1 file2 file3 | grep foo. Теперь,grep foo file1это просто частный случайgrep foo file1 file2 file3, а если вы не относитесь к нему так же, как вы, по крайней мере, используете циклы мозговых часов, пытаясь избежать бесполезной награды cat.это приводит нас к аргументу, что
grep foo file1 file2 file3это сцепление, иcatсцепляется так, что это правильноcat file1 file2 file3а потому чтоcatне сцепляется вcat file1 | grep fooпоэтому мы нарушаем дух обоихcatи всемогущий Unix. Ну, если бы это было так, то Unix понадобилась бы другая команда, чтобы прочитать вывод одного файла и выплюнуть его stdout (не разбивайте его на страницы или что-то еще просто чистый плевок в stdout). Так что у вас будет ситуация, когда вы говоритеcat file1 file2илиdog file1и добросовестно помните, чтобы избежатьcat file1чтобы избежать получения награды, а также избежатьdog file1 file2С тех пор, надеюсь, дизайнdogвыдаст ошибку, если указано несколько файлов.надеюсь, в этот момент Вы сочувствуете дизайнерам Unix за то, что не включили отдельную команду, чтобы выплюнуть файл в stdout, а также именование
catдля конкатенации вместо того, чтобы дать ему какое-то другое имя.<edit>удалены некорректные комментарии<, фактически<является эффективным средством без копирования, чтобы выплюнуть файл в stdout, который вы можете разместить в начале конвейера, поэтому разработчики unix включили что-то специально для этого</edit>следующий вопрос: почему важно иметь команды, которые просто плюют файл или конкатенацию нескольких файлы в stdout, без дальнейшей обработки? Одна из причин заключается в том, чтобы не иметь каждую команду Unix, которая работает на стандартном входе, чтобы знать, как проанализировать хотя бы один аргумент файла командной строки и использовать его в качестве ввода, если он существует. Вторая причина заключается в том, чтобы пользователи не помнили: (a) где идут аргументы имени файла; и (b) избегайте ошибки беззвучного конвейера, как упоминалось выше.
это подводит нас к тому, почему
grepимеет дополнительную логику. Обоснование заключается в том, чтобы позволить пользователю беглость для команд, которые используются часто и на автономной основе (а не как конвейер). Это небольшой компромисс ортогональности для значительного повышения удобства использования. Не все команды должны быть разработаны таким образом, и команды, которые не часто используются, должны полностью избегать дополнительной логики аргументов файла (помните, что дополнительная логика приводит к ненужной хрупкости (возможность ошибки)). Исключение-разрешить аргументы файла, как в случаеgrep. (кстати Кстати обратите внимание, чтоlsимеет совершенно другую причину не просто принять, но в значительной степени требуют аргументов файла)наконец, что можно было бы сделать лучше, если бы такие исключительные команды, как
grep(но не обязательноls) генерирует ошибку, если стандартный ввод также доступен при указании аргументов файла. Это разумно, потому что команды включают логику, которая нарушает ортогональный дух Unix для удобства пользователя. Для дальнейшего удобства пользователя, то есть для предотвращения страданий, вызванных молчаливым отказом, такие команды не должны колебаться нарушать свое собственное нарушение, имея дополнительную логику, чтобы предупредить пользователя, если есть возможность молчаливой ошибки.
Неа!
прежде всего, не имеет значения, где в команде происходит перенаправление. Поэтому, если вам нравится перенаправление слева от вашей команды, это нормально:
< somefile commandэто то же самое, что
command < somefileво-вторых, есть n + 1 процессы и подоболочка происходит, когда вы используете трубу. Это определенно медленнее. В некоторых случаях n было бы равно нулю (например, когда вы перенаправляете на встроенную оболочку), поэтому используя
catвы добавляете новый процесс совершенно излишне.как обобщение, всякий раз, когда вы обнаружите, что используете трубу, стоит потратить 30 секунд, чтобы увидеть, можете ли вы ее устранить. (Но, вероятно, не стоит брать намного больше, чем 30 секунд.) Вот несколько примеров, когда трубы и процессы часто используются без необходимости:
for word in $(cat somefile); … # for word in $(<somefile); … (or better yet, while read < somefile) grep something | awk stuff; # awk '/something/ stuff' (similar for sed) echo something | command; # command <<< something (although echo would be necessary for pure POSIX)не стесняйтесь редактировать, чтобы добавить больше примеров.
С версией UUoC,
catдолжен прочитать файл в память, затем записать его в канал, и команда должна прочитать данные из канала, поэтому ядро должно скопировать весь файл три раз, тогда как в перенаправленном случае ядро должно скопировать файл только один раз. Быстрее сделать что-то один раз, чем сделать это три раза.использование:
cat "$@" | commandэто совершенно другое и не обязательно бесполезное использование
cat. Это по-прежнему бесполезно, если команда является стандартным фильтром, который принимает ноль или более аргументов имени файла и обрабатывает их по очереди. Рассмотримtrcommand: это чистый фильтр, который игнорирует или отклоняет аргументы имени файла. Чтобы передать ему несколько файлов, вы должны использоватьcatкак показано на рисунке. (Конечно, есть отдельное обсуждение, что конструкцияtrне очень хорошо; нет никакой реальной причины, по которой он не мог быть разработан как стандартный фильтр.) Это также может быть действительным, если вы хотите команда для обработки всех входных данных как один файл, а не как несколько отдельных файлов, даже если команда будет принимать несколько отдельных файлов: например,wcтакая команда.это
cat single-fileслучай, который безусловно бесполезен.
Я не согласен с большинством случаев чрезмерно самодовольной награды UUOC, потому что, когда вы учите кого-то другого,
catявляется удобным местом-держателем для любой команды или жесткого сложного конвейера команд, которые производят вывод, подходящий для обсуждаемой проблемы или задачи.это особенно верно на таких сайтах, как Stack Overflow, ServerFault, Unix & Linux или любой из сайтов SE.
если кто-то специально спрашивает об оптимизации, или если вы хотите добавить дополнительная информация об этом тогда, отлично, расскажет о том, как использовать cat неэффективно. Но не ругайте людей, потому что они решили стремиться к простоте и легкости понимания в своих примерах, а не смотреть на меня, как я крут! сложность.
короче говоря, потому что кошка не всегда кошка.
кроме того, потому что большинство людей, которым нравится ходить вокруг награждения UUOCs, делают это, потому что они больше озабочены тем, чтобы показать, насколько они "умны", чем они помогают или обучение людей. На самом деле, они демонстрируют, что они, вероятно, просто еще один новичок, который нашел крошечную палку, чтобы бить своих сверстников.
обновление
вот еще один UUOC, который я опубликовал в ответе на https://unix.stackexchange.com/a/301194/7696:
sqlq() { local filter filter='cat' # very primitive, use getopts for real option handling. if [ "" == "--delete-blank-lines" ] ; then filter='grep -v "^$"' shift fi # each arg is piped into sqlplus as a separate command printf "%s\n" "$@" | sqlplus -S sss/eee@sid | $filter }педанты UUOC сказали бы, что это UUOC, потому что это легко можно сделать
$filterпо умолчанию пустая строка и естьifзаявлениеfilter='| grep -v "^$"'но Имо, не вставляя символ трубы в$filterэтот "бесполезный"catслужит чрезвычайно полезной цели самодокументирования того факта, что$filterнаprintfline-это не просто еще один аргумент дляsqlplus, это дополнительный выбираемый пользователем выходной фильтр.если есть необходимость иметь несколько дополнительных выходных фильтров, обработка параметров может просто добавить
| whateverдо$filterтак часто, как это необходимо - один дополнительныйcatв конвейере нет это может повредить что-либо или вызвать заметную потерю производительности.
дополнительная проблема заключается в том, что труба может молча маскировать подоболочку. Для этого примера я заменю
catСecho, но та же проблема существует.echo "foo" | while read line; do x=$line done echo "$x"вы могли бы ожидать
xсодержатfoo, но это не так.xвы установили был в подрешетке породил для выполненияwhileпетли.xв оболочке, которая запустила конвейер, имеет несвязанное значение или вообще не задано.в bash4, вы можете настроить некоторые параметры оболочки так что последняя команда конвейера выполняется в той же оболочке, что и та, которая запускает конвейер, но тогда вы можете попробовать это
echo "foo" | while read line; do x=$line done | awk '...'и
xснова локально дляwhile's-подуровне.
сценарий оболочки очень похож на язык копирования/вставки. Для большинства людей, которые пишут скрипты, они не в нем, выучить язык, это просто препятствие, они должны преодолеть, чтобы продолжать делать вещи на языке(языках) они вообще-то знакомы.
в этом контексте я рассматриваю его как разрушительный и потенциально даже разрушительно распространять различные анти-шаблоны сценариев оболочки. Код, который кто-то находит при переполнении стека, в идеале должен быть возможен для копирования/вставки в их среду с минимальными изменениями и неполным пониманием.
редактирование кода может быть проблематично потому что легко внести изменения, которые не были предназначены автором кода. Поэтому мы склонны оставлять комментарии, чтобы предложить изменения в коде.комментарии UUCA и связанные с ними антипаттерны предназначены не только для авторов кода, который мы комментируем; они так же a предостережение emptor помочь читателей на сайте становится известно о проблемах в коде, который они находят здесь.
мы не можем надеяться достичь ситуации, когда нет ответы на переполнение стека рекомендуют бесполезно
cats (или некотируемые переменные, илиchmod 777, или большое разнообразие других антипаттернов), но мы можем, по крайней мере, помочь обучить пользователя, который собирается скопировать/вставить этот код в самый внутренний жесткий цикл своего скрипта, который выполняется миллионы раз.
на обороны КПП:
да
< input process > outputили
process < input > outputявляется более эффективным, но многие вызовы не имеют проблем с производительностью, так что вам все равно.
эргономическим причинам:
мы привыкли читать слева направо, так что команда, как
cat infile | process1 | process2 > outfileэто тривиально, чтобы понять.
process1 < infile | process2 > outfileдолжен перепрыгнуть через process1, а затем читать слева направо. Это может быть исцелен:
< infile process1 | process2 > outfileвыглядит как-то, как будто есть стрелка, указывающая налево, где ничего нет. Более запутанным и похожим на причудливое цитирование является:
process1 > outfile < infileи генерация скриптов часто является итерационным процессом,
cat file cat file | process1 cat file | process1 | process2 cat file | process1 | process2 > outfileгде вы видите свой прогресс шаг за шагом, в то время как
< fileне работает даже. Простые способы менее подвержены ошибкам, а эргономичная командная катенация проста с cat.
еще одна тема, что большинство людей подвергались воздействию > и
и сравнение двух операндов С является коммутативным, противопоказания, что означает
(a > b) == (b < a)я помню, как в первый раз использовал
a.sh < fileможет означать то же самое, что
file > a.shи как-то переписать мой a.sh сценарий. Возможно, это проблема для многих новичков.
редкие различия
wc -c journal.txt 15666 journal.txt cat journal.txt | wc -c 15666последний может быть использован в расчетах непосредственно.
factor $(cat journal.txt | wc -c)конечно
< journal.txt wc -c 15666 wc -c < journal.txt 15666но кого это волнует-15k?
если бы я иногда сталкивался с проблемами, конечно, я бы изменил свою привычку вызывать кошку.
при использовании очень больших или многих, многих файлов, избегая cat штраф. Для большинства вопросов использование cat является ортогональным, вне темы, а не проблемой.
начиная эти бесполезные бесполезное использование обсуждения кошки на каждую вторую тему оболочки только раздражает и скучно. Получить жизнь и ждать Минуты славы, когда дело с вопросами производительности.
Я думаю ,что (традиционный способ) с помощью трубы немного быстрее; на моем поле я использовал чтобы увидеть, что происходит:
без трубы:
toc@UnixServer:~$ strace wc -l < wrong_output.c execve("/usr/bin/wc", ["wc", "-l"], [/* 18 vars */]) = 0 brk(0) = 0x8b50000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0 mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3 read(3, "7ELFp204"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0 mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000 mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000 mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0xb779f000, 8192, PROT_READ) = 0 mprotect(0x804f000, 4096, PROT_READ) = 0 mprotect(0xb77ce000, 4096, PROT_READ) = 0 munmap(0xb77a5000, 29107) = 0 brk(0) = 0x8b50000 brk(0x8b71000) = 0x8b71000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000 mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000 close(3) = 0 open("/usr/share/locale/locale.alias", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000 read(3, "# Locale name alias data base.\n#"..., 4096) = 2570 read(3, "", 4096) = 0 close(3) = 0 munmap(0xb77ac000, 4096) = 0 open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0 mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000 close(3) = 0 open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0 mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000 close(3) = 0 read(0, "#include<stdio.h>\n\nint main(int "..., 16384) = 180 read(0, "", 16384) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000 write(1, "13\n", 313 ) = 3 close(0) = 0 close(1) = 0 munmap(0xb7260000, 4096) = 0 close(2) = 0 exit_group(0) = ?и с трубой:
toc@UnixServer:~$ strace cat wrong_output.c | wc -l execve("/bin/cat", ["cat", "wrong_output.c"], [/* 18 vars */]) = 0 brk(0) = 0xa017000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0 mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3 read(3, "7ELFp204"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0 mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000 mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000 mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0xb773d000, 8192, PROT_READ) = 0 mprotect(0x8051000, 4096, PROT_READ) = 0 mprotect(0xb776c000, 4096, PROT_READ) = 0 munmap(0xb7743000, 29107) = 0 brk(0) = 0xa017000 brk(0xa038000) = 0xa038000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000 mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000 close(3) = 0 fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0 read(3, "#include<stdio.h>\n\nint main(int "..., 32768) = 180 write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180 read(3, "", 32768) = 0 close(3) = 0 close(1) = 0 close(2) = 0 exit_group(0) = ? 13вы можете сделать некоторые испытания с
straceиtimeкоманда с большим количеством и более длинными командами для хорошего бенчмаркинга.