이 질문의 동기는 "역방향 가져오기", 거대한 파일을 아래에서 위로 greping하는 방법에 대해 설명합니다.
tac file | grep whatever
또는 더 효율적으로:
grep whatever < <(tac file)
< <(tac filename)
파이프만큼 빨라야 합니다 .
다른 사용자들의 흥미로운 의견도 많이 있습니다.
내 질문:
|
이들그리고 그리고 의 차이점은 무엇인가요< <()
?- 하나가 다른 것보다 빠른 이유는 무엇입니까?
- 어느 것이 실제로 더 빠른가요?
- 왜 아무도 제안하지 않았나요
xargs
?
답변1
이 구성 <(tac file)
으로 인해 쉘이 생성됩니다.
- 이름으로 파이프 만들기
- Linux 및 SysV와 같은 시스템에서는
/dev/fd
일반 파이프를 사용하고/dev/fd/<the-file-descriptor-of-the-pipe>
이를 이름으로 사용합니다. - 다른 시스템에서는 디스크에 실제 파일 항목을 생성해야 하는 명명된 파이프가 사용됩니다.
- Linux 및 SysV와 같은 시스템에서는
- 명령을 시작
tac file
하고 파이프의 한쪽 끝에 연결합니다. - 명령줄의 전체 구조를 파이프 이름으로 바꿉니다.
교체 후 명령줄은 다음과 같습니다.
grep whatever < /tmp/whatever-name-the-shell-used-for-the-named-pipe
그런 다음 grep
실행하려면 표준 입력(예: 파이프)을 읽고, 읽은 후 첫 번째 인수를 검색합니다.
그래서 최종 결과는 동일합니다 ...
tac file | grep whatever
...동일한 두 프로그램이 실행되고 파이프를 사용하여 연결하기 때문입니다. 그러나 <( ... )
빌드 프로세스는 더 많은 단계를 포함하고 임시 파일(이름이 지정된 파이프)을 포함할 수 있으므로 더 복잡합니다.
이 <( ... )
구성은 확장이며 /dev/fd
표준 POSIX bourne 쉘이나 파이프를 지원하지 않거나 명명된 플랫폼에서는 사용할 수 없습니다. 이러한 이유만으로도 고려 중인 두 가지 대안은 기능적으로 동일하므로 이식성이 뛰어난 command | other-command
형태가 더 나은 선택입니다.
추가 컨볼루션으로 인해 빌드 <( ... )
속도가 느려져야 하지만 이는 시작 단계에만 해당되며 차이를 쉽게 측정할 수 없다고 생각합니다.
노트: Linux SysV 플랫폼에서는 < ( ... )
명명된 파이프가 사용되지 않고 대신 일반 파이프가 사용됩니다. 일반 파이프(실제로 모든 파일 설명자)는 특별한 이름으로 참조될 수 있으므로 /dev/fd/<file-descriptor-number
쉘은 해당 이름을 파이프 이름으로 사용합니다. 이렇게 하면 실제 파일 시스템에서 실제 임시 파일 이름을 사용하여 실제 명명된 파이프가 생성되는 것을 방지할 수 있습니다. /dev/fd
이 기능이 에 처음 등장했을 때 구현하는 데 사용된 트릭이지만 이는 ksh
최적화입니다. 이 기능을 지원하지 않는 플랫폼에서는 위에서 설명한 대로 실제 파일 시스템의 일반 명명된 파이프가 사용됩니다.
또한 참고하시기 바랍니다: 문법이 <<( ... )
오해의 소지가 있다고 설명합니다. 실제로는 <( ... )
파이프 이름으로 대체되고 <
이 구문과 별도로 모든 항목 앞에 붙는 또 다른 문자가 사용되며 파일에서 입력을 리디렉션하는 잘 알려진 일반적인 구문입니다.
답변2
|와 <<()의 차이점은 무엇입니까?
그들 사이에는 차이점이 있습니다.
|
각 명령이 별도의 하위 쉘에서 실행되도록 합니다.<()
백그라운드에서 대체되는 명령을 실행합니다.
다음 두 가지 질문에 대해 우리는 몇 가지를 할 것입니다 strace
:
pipe
:
$ strace -fc bash -c 'tac /usr/share/dict/american-english | grep qwerty'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.008120 2707 3 1 wait4
0.00 0.000000 0 352 read
0.00 0.000000 0 229 write
0.00 0.000000 0 20 2 open
0.00 0.000000 0 29 2 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 117 lseek
0.00 0.000000 0 38 mmap
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 25 brk
0.00 0.000000 0 22 rt_sigaction
0.00 0.000000 0 18 rt_sigprocmask
0.00 0.000000 0 1 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 24 12 access
0.00 0.000000 0 1 pipe
0.00 0.000000 0 2 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 2 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 0.008120 1034 37 total
Process Substitution
:
$ strace -fc bash -c 'grep qwerty < <(tac /usr/share/dict/american-english)'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.14 0.016001 4000 4 2 wait4
0.46 0.000075 0 229 write
0.24 0.000038 0 341 read
0.16 0.000026 1 24 brk
0.00 0.000000 0 21 2 open
0.00 0.000000 0 27 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 117 lseek
0.00 0.000000 0 38 mmap
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 35 rt_sigaction
0.00 0.000000 0 24 rt_sigprocmask
0.00 0.000000 0 2 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 24 12 access
0.00 0.000000 0 1 pipe
0.00 0.000000 0 3 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 3 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 1 fcntl
0.00 0.000000 0 2 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 0.016140 1046 37 total
왜 어떤 것들은 다른 것보다 더 빠른가요?
실제로 더 빠른 것은 무엇입니까?
process substitution
더 많은 시스템 호출을 사용하기 때문에 이 예제보다 속도가 느린 것을 볼 수 있습니다 . pipe
둘 다 하위 프로세스를 기다리는 데 많은 시간을 소비하지만 process substitution
더 많은 시스템 호출을 사용 wait4()
하고 각 호출은 pipe
.
왜 아무도 xargs를 제안하지 않았나요?
나는 여기에 많은 도움이 있다고 생각하지 않습니다 xargs
. 그것은 그 일이 아닙니다.
고쳐 쓰다
@Gilles가 제안한 대로 더 큰 파일을 사용했습니다( /dev/urandom
. 에서 . 파일이 pipe
실제로 process substitution
.
pipe
:
$ strace -fc bash -c 'tac sample.txt | grep qwerty'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
81.15 8.284959 2761653 3 1 wait4
17.89 1.825959 2 780959 read
0.91 0.092708 0 524286 write
0.05 0.005364 0 262146 lseek
0.00 0.000000 0 20 2 open
0.00 0.000000 0 29 2 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 38 mmap
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 25 brk
0.00 0.000000 0 22 rt_sigaction
0.00 0.000000 0 18 rt_sigprocmask
0.00 0.000000 0 1 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 24 12 access
0.00 0.000000 0 1 pipe
0.00 0.000000 0 2 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 2 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 10.208990 1567727 37 total
process substitution
:
$ strace -fc bash -c 'grep qwerty < <(tac sample.txt)'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.51 13.912869 3478217 4 2 wait4
0.38 0.053373 0 655269 read
0.09 0.013084 0 524286 write
0.02 0.002454 0 262146 lseek
0.00 0.000030 1 38 mmap
0.00 0.000024 1 24 12 access
0.00 0.000000 0 21 2 open
0.00 0.000000 0 27 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 24 brk
0.00 0.000000 0 35 rt_sigaction
0.00 0.000000 0 24 rt_sigprocmask
0.00 0.000000 0 2 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 1 pipe
0.00 0.000000 0 3 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 3 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 1 fcntl
0.00 0.000000 0 2 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 13.981834 1442060 37 total
답변3
나는 ~였다아니요표시된 결과를 복사하는 기능쿠엔람. 2GB 파일의 경우에도 MacOS Mojave의 Bash 5에서는 프로세스 교체와 파이프 사이의 시간이 매우 유사합니다. 호출과 관련된 오버헤드가 다음과 같기 때문에 이는 나에게 의미가 있습니다.가장 작은2GB 파일에 대한 호출의 실제 처리와 비교할 때, 파이프의 한 번의 반복으로 프로세스 대체를 사용하는 것은 임의성/파일 내용을 캐시하기 위해 먼저 실행되는 명령에 따라 달라집니다.
나예전에는연구 결과를 재현하는 능력이 질문에는이는 다음을 나타냅니다.파이프보다 공정 교체가 빠릅니다.이러한 전화는 수천 번 이루어졌습니다.
이것은 내가 실행한 명령과 결과입니다.
파이프.sh:
shopt -s lastpipe
for i in {1..5000}; do
echo foo bar |
while read; do
echo $REPLY >/dev/null
done
done
proc-sub.sh:
for i in {1..5000}; do
while read; do
echo $REPLY >/dev/null
done < <(echo foo bar)
done
최종 파이프가 없는 파이프.sh:
for i in {1..5000}; do
echo foo bar |
while read; do
echo $REPLY >/dev/null
done
done
시험:
time ./proc-sub.sh
real 0m9.505s
user 0m1.875s
sys 0m10.705s
time ./pipe.sh
real 0m14.036s
user 0m4.583s
sys 0m14.193s
time ./pipe-no-lastpipe.sh
real 0m16.696s
user 0m3.055s
sys 0m18.057s