В чем разница между «cat

Простейшим способом отображения содержимого файла является использование строковой команды cat:

cat file.txt

Тот же результат, который я получаю, используя перенаправление ввода:

cat < file.txt

Тогда, что чем они отличаются?

15
задан 21 November 2019 в 14:01

5 ответов

Нет никакого различия. Эти команды делают то же самое.

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

существует почти всегда много способов получить тот же результат.

cat принимает файл от аргументов или stdin, при отсутствии аргументов.

См. man cat

SYNOPSIS
       cat [OPTION]... [FILE]...

DESCRIPTION
       Concatenate FILE(s) to standard output.

       With no FILE, or when FILE is -, read standard input.
7
ответ дан 23 November 2019 в 02:38
cat file

cat программа откроется, считать и закрыть файл.

cat < file

Ваша оболочка откроет файл и подключит содержание с cat stdin. cat распознает, что это не имеет никаких аргументов файла и будет читать из stdin.

47
ответ дан 23 November 2019 в 02:38

Одна Большая разница

На большой разнице cat без перенаправления, обработает подстановочные знаки. С перенаправлением происходит сообщение об ошибке:

$ cat * > /dev/null

$ cat < * > /dev/null
bash: *: ambiguous redirect

Одно Крошечное Различие

я думал с перенаправлением, это будет медленнее, но нет никакой заметной разницы во времени:

$ time for f in * ; do cat "$f" > /dev/null ; done

real    0m3.399s
user    0m0.130s
sys     0m1.940s

$ time for f in * ; do cat < "$f" > /dev/null ; done

real    0m3.430s
user    0m0.100s
sys     0m2.043s

Примечания:

  • различие о 1/1000-м (1 тысячное) секунды в этом тесте. В других тестах это было 1/100-м из секунды, которая является, все еще не может быть замечен.
  • Альтернатива тесты несколько раз так данные кэшируются в RAM как можно больше, и возвращаются более последовательные времена сравнения. Другая опция состоит в том, чтобы отбросить все кэши перед каждым тестом.
12
ответ дан 23 November 2019 в 02:38

Существенное различие - то, кто открывает файл, оболочку или кошку. Они могут работать с различными режимами разрешения, таким образом

sudo cat /proc/some-protected-file

может работать, в то время как

sudo cat < /proc/some-protected-file

перестанет работать. Этот вид режима разрешения может быть немного хитрым для работы вокруг, просто желая использовать echo для легких сценариев, таким образом, существует целесообразность неправильного использования tee как в

echo level 7|sudo tee /proc/acpi/ibm/fan

, который действительно не работает с помощью перенаправления вместо этого из-за проблемы разрешения.

4
ответ дан 23 November 2019 в 02:38

TL;DR Версия ответа:

  • С cat file.txt приложение (в данном случае cat ) получило один позиционный параметр , выполняет на нем системный вызов open(2), а проверки разрешений происходят внутри приложений.

  • С cat < file.txt оболочка выполнит системный вызов dup2(), чтобы преобразовать стандартный ввод в копию дескриптора файла (обычно следующего доступного, например 3), соответствующего file.txt и закройте этот файловый дескриптор (например, 3). Приложение не выполняет open(2) для файла и не знает о существовании файла; он работает строго со своим файловым дескриптором stdin. Проверка разрешений возлагается на оболочку. Описание открытого файла останется таким же, как при открытии файла оболочкой.

Введение

На поверхности cat file.txt и cat < файл.txt ведут себя одинаково, но за кулисами происходит гораздо больше с этой единственной разницей в символах. Этот символ < меняет то, как оболочка понимает file.txt, кто открывает файл и как файл передается между оболочкой и командой. Конечно, для того, чтобы объяснить все эти детали, нам также необходимо понять, как открытие файлов и выполнение команд работает в оболочке, и именно на это направлен мой ответ - рассказать читателю в самых простых словах о том, что на самом деле происходит в эти, казалось бы, простые команды. В этом ответе вы найдете несколько примеров, в том числе те, которые используют команду strace для резервного копирования объяснений того, что на самом деле происходит за кулисами.

Поскольку внутренняя работа оболочек и команд основана на стандартных системных вызовах, важно рассматривать cat как одну из многих других команд. Если вы новичок, читающий этот ответ, будьте непредвзяты и помните, что prog file.txt не всегда будет таким же, как prog . Другая команда может вести себя совершенно по-разному, когда к ней применяются две формы, и это зависит от разрешений или от того, как написана программа. Прошу вас также воздержаться от суждений и посмотреть на это с точки зрения разных пользователей - для случайного пользователя оболочки потребности могут быть совершенно другими, чем для сисадмина и разработчика.

Системный вызов execve() и позиционные параметры, которые видит исполняемый файл

Оболочки запускают команды, создавая дочерний процесс с системным вызовом fork(2) и вызывая системный вызов execve(2), который выполняет команду с указанными аргументами и переменными среды. Команда, вызываемая внутри execve(), возьмет на себя управление и заменит процесс; например, когда оболочка вызывает cat, она сначала создает дочерний процесс с PID 12345, а после execve() PID 12345 становится cat.

Это подводит нас к различию между cat file.txt и cat < file.txt. В первом случае cat file.txt — это команда, вызываемая с одним позиционным параметром, и оболочка соберет вместе execve() соответствующим образом:

$ strace -e execve cat testfile.txt
execve("/bin/cat", ["cat", "testfile.txt"], 0x7ffcc6ee95f8 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++

Во втором случае Часть < является оператором оболочки, а < testfile.txt указывает оболочке открыть testfile.txt и преобразовать дескриптор файла stdin 0 в копию дескриптора файла, который соответствует в testfile.txt. Это означает, что < testfile.txt не будет передаваться самой команде в качестве позиционного аргумента:

$ strace -e execve cat < testfile.txt
execve("/bin/cat", ["cat"], 0x7ffc6adb5490 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++
$ 

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

STDIN и файловые дескрипторы

Кто открывает файл — cat или оболочка? Как они его открывают? У них вообще есть разрешение открыть его? Это вопросы, которые можно задать, но сначала нам нужно понять, как работает открытие файла.

Когда процесс выполняет open() или openat() над файлом, эти функции предоставляют процессу целое число, соответствующее открытому файлу,затем программы могут вызывать вызовы read(), seek() и write() и множество других системных вызовов, ссылаясь на это целое число. Конечно, система (также известная как ядро) будет хранить в памяти, как был открыт конкретный файл, с какими разрешениями, в каком режиме — только чтение, только запись, чтение/запись — и где в файле мы сейчас находимся. - в байте 0 или байте 1024 - что называется смещением. Это называется открыть описание файла.

На самом базовом уровне cat testfile.txt — это место, где cat открывает файл, и на него будет ссылаться следующий доступный файловый дескриптор, равный 3 (обратите внимание на 3 в чтение(2)).

$ strace -e read -f cat testfile.txt > /dev/null
...
read(3, "hello, I am testfile.txt and thi"..., 131072) = 79
read(3, "", 131072)                     = 0
+++ exited with 0 +++

Напротив, cat < testfile.txt будет использовать файловый дескриптор 0 (также известный как stdin):

$ strace -e read -f cat < testfile.txt > /dev/null
...
read(0, "hello, I am testfile.txt and thi"..., 131072) = 79
read(0, "", 131072)                     = 0
+++ exited with 0 +++

Помните, как ранее мы узнали, что оболочки сначала запускают команды через fork() затем exec() тип процесса? Оказывается, то, как открыт файл, влияет на дочерние процессы, созданные с помощью шаблона fork()/exec(). Процитируем open(2) manual:

Когда файловый дескриптор дублируется (используя dup(2) или подобное), дубликат относится к тому же описанию открытого файла, что и оригинал файловый дескриптор, и, следовательно, два файловых дескриптора совместно используют флаги смещения файла и состояния файла. Такое совместное использование также может происходить между процессами: дочерний процесс, созданный с помощью fork(2), наследует дубликаты файловых дескрипторов своего родителя, и эти дубликаты обратитесь к тем же описаниям открытых файлов

Что это означает для cat file.txt и cat ? На самом деле много. В cat file.txt cat открывает файл, что означает, что он контролирует, как файл открывается. Во втором случае оболочка откроет файл file.txt, и способ его открытия останется неизменным для дочерних процессов, составных команд и конвейеров. Где мы сейчас находимся в файле, также останется прежним.

В качестве примера возьмем этот файл:

$ cat testfile.txt 
hello, I am testfile.txt and this is first line
line two
line three

last line

Посмотрите на пример ниже. Почему слово строка не изменилось в первой строке?

$ { head -n1; sed 's/line/potato/'; }  <  testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
potato two
potato three

last potato

Ответ кроется в цитате из руководства open(2) выше: файл, открытый оболочкой, дублируется на стандартный ввод составной команды, и каждая выполняемая команда/процесс разделяет смещение описания открытого файла. head просто перематывал файл на одну строку вперед, а sed обрабатывал все остальное. В частности, мы увидим 2 последовательности системных вызовов dup2()/fork()/execve(), и в каждом случае мы получим копию дескриптор файла, который ссылается на то же описание файла в открытом testfile.txt.Смущенный ? Давайте возьмем немного более сумасшедший пример:

$ { head -n1; dd of=/dev/null bs=1 count=5; cat; }  <  testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
two
line three

last line

Здесь мы напечатали первую строку, затем перемотали описание открытого файла на 5 байт вперед (что исключило слово строка ), а затем просто напечатали остальное. И как нам это удалось? Описание открытого файла в testfile.txt остается прежним, с общим смещением в файле.

Теперь, почему это полезно понимать, кроме написания сумасшедших составных команд, подобных приведенным выше? Как разработчик, вы можете воспользоваться преимуществом или остерегаться такого поведения. Скажем, вместо cat вы написали программу на C, которой нужна конфигурация либо в виде файла, либо из стандартного ввода, и вы запускаете ее как myprog myconfig.json. Что произойдет, если вместо этого вы запустите { head -n1; myprog;} < myconfig.json ? В лучшем случае ваша программа получит неполные данные конфигурации, а в худшем - сломает программу. Мы также можем использовать это как преимущество, чтобы создать дочерний процесс и позволить родителю перемотать данные, о которых должен позаботиться дочерний процесс.

Разрешения и привилегии

Давайте на этот раз начнем с примера файла, у которого нет прав на чтение или запись для других пользователей:

$ sudo -u potato cat < testfile.txt
hello, I am testfile.txt and this is first line
line two
line three

last line
$ sudo -u potato cat testfile.txt
cat: testfile.txt: Permission denied

Что здесь произошло? Почему мы можем прочитать файл в первом примере как пользователь potato, но не во втором? Это восходит к той же цитате из справочной страницы open(2), упомянутой ранее. С < file.txt оболочка открывает файл, поэтому проверки разрешений происходят во время open/openat() , выполняемых оболочкой.В это время оболочка запускается с привилегиями владельца файла, у которого есть права на чтение файла. В силу того, что описание открытого файла наследуется через вызовы dup2, оболочка передает копию дескриптора открытого файла в sudo, который передает копию дескриптора файла в cat , и cat, не подозревая ни о чем другом, с удовольствием читает содержимое файла. В последней команде cat под пользователем картофеля выполняет open() над файлом,и, конечно же, у этого пользователя нет прав на чтение файла.

Более практично и чаще всего, вот почему пользователи недоумевают, почему что-то подобное не работает (запуск привилегированной команды для записи в файл, который они не могут открыть):

$ sudo echo 100 > /sys/class/drm/*/intel_backlight/brightness
bash: /sys/class/drm/card0-eDP-1/intel_backlight/brightness: Permission denied

Но что-то вроде этого работает (используя привилегированная команда для записи в файл, который не требует привилегий):

$ echo 100 |sudo tee /sys/class/drm/*/intel_backlight/brightness
[sudo] password for administrator: 
100

Теоретический пример ситуации, противоположной той, что я показал ранее (где привилегированная_программа < файл.txt не выполняется, но привилегированная_прог файл.txt действительно работает) будет с программами SUID. SUID-программы, такие как passwd , позволяют выполнять действия с разрешениями владельца исполняемого файла. Вот почему команда passwd позволяет вам изменить свой пароль, а затем записать это изменение в /etc/shadow, даже если файл принадлежит пользователю root.

И ради примера и развлечения я на самом деле пишу быстрое демо-приложение, похожее на cat, на C (исходный код здесь) с установленным битом SUID, но если вы получите пункт - не стесняйтесь переходить к следующему разделу этого ответа и игнорировать эту часть. Примечание: ОС игнорирует бит SUID в интерпретируемых исполняемых файлах с #! , так что версия той же самой вещи на Python потерпит неудачу.

Давайте проверим права доступа к программе и testfile.txt:

$ ls -l ./privileged
-rwsr-xr-x 1 administrator administrator 8672 Nov 29 16:39 ./privileged
$ ls -l testfile.txt
-rw-r----- 1 administrator administrator 79 Nov 29 12:34 testfile.txt

Выглядит хорошо, только владелец файла и те, кто входит в группу administrator, могут прочитать этот файл.Теперь давайте войдем в систему как пользователь картофеля и попробуем прочитать файл:

$ su potato
Password: 
potato@my-PC:/home/administrator$ cat ./testfile.txt
cat: ./testfile.txt: Permission denied
potato@my-PC:/home/administrator$ cat  < ./testfile.txt
bash: ./testfile.txt: Permission denied

Выглядит нормально, ни оболочка, ни cat, имеющие права пользователя картофеля, не могут прочитать файл, чтение которого им не разрешено. Обратите также внимание, кто сообщает об ошибке — cat против bash. Давайте проверим нашу программу SUID:

potato@my-PC:/home/administrator$ ./privileged testfile.txt
hello, I am testfile.txt and this is first line
line two
line three

last line
potato@my-PC:/home/administrator$ ./privileged < testfile.txt
bash: testfile.txt: Permission denied

Работает, как задумано! Опять же, суть этой небольшой демонстрации заключается в том, что prog file.txt и prog < file.txt различаются тем, кто открывает файл, и различаются разрешениями на открытие файла.

Как программы реагируют на STDIN

Мы уже знаем, что < testfile.txt переписывает стандартный ввод таким образом, что данные будут поступать из указанного файла, а не с клавиатуры. Теоретически и исходя из философии Unix «делать одно и делать это хорошо», программы, читающие со стандартного ввода (он же файловый дескриптор 0), должны вести себя согласованно, и поэтому prog1 | prog2 должен быть похож на prog2 file.txt. Но что, если prog2 хочет перемотать с помощью системного вызова lseek, например, чтобы перейти к определенному байту или перемотать в конец, чтобы узнать, сколько у нас данных ?

Некоторые программы запрещают чтение данных из канала, поскольку конвейеры нельзя перемотать с помощью системного вызова lseek(2) или данные нельзя загрузить в память с помощью mmap(2) для более быстрой обработки. . Это было раскрыто в превосходном ответе Стефана Шазеласа на этот вопрос: В чем разница между «cat file | ./binary» и «./binary < file”? Я настоятельно рекомендую это прочитать.

К счастью, cat < file.txt и cat file.txt ведут себя последовательно, и cat никоим образом не против каналов, хотя мы знаем, что это читается совершенно разные файловые дескрипторы. Как это применимо в prog file.txt и prog в целом? Если программа действительно не хочет ничего делать с каналами, отсутствие позиционного параметра file.txt будет достаточным для выхода с ошибкой, но приложение все равно может использовать lseek() на stdin, чтобы проверить, является ли он каналом или нет (хотя isatty(3) или обнаружение режима S_ISFIFO в fstat(2) чаще используются для обнаружения ввода канала), в в этом случае выполнение чего-то вроде ./binary <(файл шаблона grep.txt) или ./binary < <(файл шаблона grep.txt) может не сработать.

Влияние типа файла

Тип файла может влиять на поведение prog file и prog < file. Что в некоторой степени подразумевает, что как пользователь программы вы выбираете системные вызовы, даже если вы не знаете об этом. Например, предположим, что у нас есть сокет домена Unix, и мы запускаем сервер nc для его прослушивания, возможно, мы даже подготовили некоторые данные для обслуживания.

$ nc -U -l /tmp/mysocket.sock   < testfile.txt 

В данном случае /tmp/mysocket.sock будет открыт с помощью разных системных вызовов:

socket(AF_UNIX, SOCK_STREAM, 0)         = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_UNIX, sun_path="/tmp/mysocket.sock"}, 20) = 0

Теперь попробуем прочитать данные из этого сокета в другом терминале:

$ cat /tmp/mysocket.sock
cat: /tmp/mysocket.sock: No such device or address
$ cat <  /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address

И shell, и cat выполняют системный вызов open(2) на что требует совершенно другого системного вызова - пары сокета (2) и соединения (2). Даже это не работает:

$ nc -U  < /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address

Но если мы осознаем тип файла и то, как мы можем вызвать правильный системный вызов, мы можем получить желаемое поведение:

$ nc -U /tmp/mysocket.sock
hello, I am testfile.txt and this is first line
line two
line three

last line

Примечания и другие рекомендуемые материалы:

9
ответ дан 29 November 2019 в 10:36

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

Похожие вопросы: