고양이의 쓸데없는 사용?


101

이것은 아마도 다음을 사용하는 대신 많은 FAQ에 있습니다.

cat file | command

(이것은 고양이의 쓸모없는 사용이라고 함) 올바른 방법은 다음과 같습니다.

command < file

둘째, "올바른"방법-OS는 추가 프로세스를 생성 할 필요가 없습니다.
그 사실을 알면서도 쓸모없는 고양이를 두 가지 이유로 계속 사용했습니다.

  1. 더 심미적-데이터가 왼쪽에서 오른쪽으로 만 균일하게 이동하는 것이 좋습니다. 그리고 쉽게 대체 할 cat다른 뭔가 ( gzcat, echo, ...), 제 2의 파일을 추가하거나 새 필터를 삽입 ( pv, mbuffer, grep...).

  2. 어떤 경우에는 더 빠를 수 있다고 "느낀다". 두 개의 프로세스가 있기 때문에 더 빠릅니다. 첫 번째 ( cat)는 읽기를 수행하고 두 번째는 무엇이든 수행합니다. 그리고 그들은 병렬로 실행될 수 있으며, 이는 때때로 더 빠른 실행을 의미합니다.

내 논리가 맞습니까 (두 번째 이유)?


22
cat신원 파이프 . 입력을 출력으로 만 스트리밍합니다. 체인의 두 번째 프로그램이 전달하는 동일한 인수 cat(또는 인수를 전달하지 않는 경우 표준 입력)에서 입력을받을 수 있다면, 절대적으로 쓸모cat없으며 추가 프로세스가 분기되고 추가 파이프가 생성됩니다. 만들어진.
Frédéric Hamidi

11
@ FrédéricHamidi cat에 인수가 없거나 인수가 -인 경우 식별 파이프입니다. 대시가 아닌 파일 이름 인수가 두 개 이상 있으면 식별 파이프 이상이되고 실제 목적을 수행하기 시작합니다.
kojiro

3
partmaps.org에 대한 이전의 인기 링크는 불행히도 죽었습니다. 콘텐츠는 현재 porkmail.org/era/unix/award.html에 있습니다
tripleee


2
올바른 데이터 흐름 (이유 1)을 표시하려면에서와 같이 명령 앞에 파일 리디렉션을두면 <file command1 | command2미학에 대한 의견이 일치하지 않을 수 있습니다.
holdenweb 19

답변:


81

나는 오늘까지 어떤 신인 이 내 답변 중 하나에 UUOC 를 고정하려고 시도했을 때까지 상을 알지 못했습니다 . 그것은 cat file.txt | grep foo | cut ... | cut .... 나는 그에게 내 마음의 한 조각을줬고 그렇게 한 후에야 그가 수상의 기원과 그렇게하는 관행을 언급 한 링크를 방문했다. 추가 검색을 통해이 질문을 받았습니다. 다소 안타깝게도 의식적인 고려에도 불구하고 내 근거가 포함 된 답변은 없었습니다.

나는 그에게 대응할 때 방어적인 자세를 취하지 않았습니다. 결국, 어린 시절 grep foo file.txt | cut ... | cut ...에는 자주 싱글을 할 때마다 grep파일 인수의 위치를 ​​배우고 첫 번째는 패턴이고 이후의 것은 파일 이름이라는 것을 알기 때문에 명령을 작성했을 것입니다.

cat내가 질문에 대답 할 때 사용하는 것은 의식적인 선택이었습니다. 부분적으로는 "좋은 맛"(Linus Torvalds의 말에서)의 이유 때문 이었지만 주로 기능적인 이유 때문이었습니다.

후자의 이유가 더 중요하므로 먼저 설명하겠습니다. 파이프 라인을 솔루션으로 제공 할 때 재사용이 가능할 것으로 기대합니다. 파이프 라인이 끝에 추가되거나 다른 파이프 라인에 연결될 가능성이 높습니다. 이 경우 grep에 대한 파일 인수가 있으면 재사용 가능성이 떨어지며 파일 인수가 있으면 오류 메시지없이 조용히 그렇게 할 수 있습니다. I. e. grep foo xyz | grep bar xyz | wc얼마나 많은 줄을 줄 것입니다xyz및을 bar모두 포함하는 줄 수를 예상하는 동안 포함 된 . 파이프 라인에서 명령을 사용하기 전에 인수를 변경해야하는 경우 오류가 발생하기 쉽습니다. 여기에 조용한 실패의 가능성을 더하면 특히 교활한 관행이됩니다.foobar

전자의 이유도 중요하지 않습니다. 좋은 취향 "은 교육을 필요로하는 누군가가 "하지만 그렇지 않다"고 말하는 순간에 당신이 옳게 생각할 수없는 위의 침묵 실패와 같은 것들에 대한 직관적 인 잠재 의식적 근거이기 때문입니다. 쓸모없는 그 고양이 ".

그러나 나는 또한 내가 언급 한 이전의 "좋은 맛"이유를 의식하도록 노력할 것이다. 그 이유는 유닉스의 직교 디자인 정신과 관련이 있습니다. grep하지 않습니다 cutls하지 않습니다 grep. 따라서 최소한 grep foo file1 file2 file3디자인 정신에 위배됩니다. 이를 수행하는 직교 방법은 cat file1 file2 file3 | grep foo. 이제는 grep foo file1의 특수한 경우 일 뿐이며 grep foo file1 file2 file3똑같이 치료하지 않으면 쓸모없는 고양이 상을 피하기 위해 적어도 뇌 시계주기를 사용하는 것입니다.

그것은 우리를 grep foo file1 file2 file3연결하고 cat연결 하는 논쟁으로 이끈다. 그래서 그것은 적절 cat file1 file2 file3하지만 cat연결되지 않기 cat file1 | grep foo때문에 우리는 cat전능 한 유닉스와 전능 한 유닉스 의 정신을 위반하고있다 . 글쎄, 만약 그렇다면 Unix는 한 파일의 출력을 읽고 stdout에 뱉어내는 다른 명령이 필요할 것입니다. (페이지를 매기거나 stdout에 대한 순수한 뱉는 것이 아닙니다). 그래서 당신은 당신이 말 cat file1 file2하거나 말하고 dog file1양심적으로 cat file1상 을 피하는 것을 피하는 것과 동시에 여러 파일이 지정된 경우 dog file1 file2의 디자인이 dog오류를 던질 것이기 때문에 피하는 상황 을 가질 것입니다.

바라건대,이 시점에서 여러분은 파일을 stdout에 뱉어내는 별도의 명령을 포함하지 cat않고 다른 이름을 지정 하는 대신 concatenate에 대한 이름 을 지정하는 Unix 설계자에게 공감합니다 . <edit>에 대한 잘못된 주석을 제거 <했습니다. 실제로 <파이프 라인의 시작 부분에 배치 할 수있는 stdout에 파일을 뱉어내는 효율적인 복사 금지 기능이므로 Unix 디자이너는이를 위해 특별히 뭔가를 포함했습니다.</edit>

다음 질문은 추가 처리없이 단순히 파일을 뱉거나 여러 파일을 stdout에 연결하는 명령을 갖는 것이 왜 중요합니까? 한 가지 이유는 표준 입력에서 작동하는 모든 단일 Unix 명령이 하나 이상의 명령 줄 파일 인수를 구문 분석하고 존재하는 경우 입력으로 사용하는 방법을 알지 못하도록하는 것입니다. 두 번째 이유는 사용자가 기억해야하는 것을 피하는 것입니다. (a) 파일 이름 인수가가는 위치; (b) 위에서 언급 한 자동 파이프 라인 버그를 피하십시오.

그것은 왜 grep추가 논리가 있는지 우리에게 가져옵니다 . 그 이유는 파이프 라인이 아닌 독립 실행 형 기반으로 자주 사용되는 명령에 대한 사용자 유창성을 허용 하는 것입니다. 유용성을 크게 높이기 위해 직교성을 약간 타협 한 것입니다. 모든 명령이 이러한 방식으로 설계되어야하는 것은 아니며 자주 사용되지 않는 명령은 파일 인수의 추가 논리를 완전히 피해야합니다 (추가 논리는 불필요한 취약성을 유발 (버그 가능성)). 예외는의 경우와 같은 파일 인수를 허용하는 것입니다 grep. (그런데, ls받아들이는 것이 아니라 파일 인수를 요구하는 완전히 다른 이유가 있음에 유의하십시오 )

마지막으로, 파일 인수가 지정 될 때 표준 입력도 사용할 수있는 경우 grep(반드시 그런 것은 아님 ls) 예외적 인 명령 이 오류를 생성하는 경우 더 잘 할 수있었습니다 .


53
참고 때 grep여러 파일 이름을 호출, 그것은 그것에서 발견 된 파일의 이름으로 발견 라인 접두사 (해당 동작을 해제하지 않는 한). 또한 개별 파일의 줄 번호를보고 할 수 있습니다. cat피드 에만 사용 하는 경우 grep파일 이름이 손실되고 행 번호는 파일 당이 아닌 모든 파일에 걸쳐 연속됩니다. 따라서 grep처리 할 cat수없는 여러 파일 자체 를 처리 해야하는 이유가 있습니다. 단일 파일 및 제로 파일 케이스는 일반적인 다중 파일 사용의 특별한 경우입니다 grep.
Jonathan Leffler

38
에서 언급 한 바와 같이 대답 하여 코지로 ,과 파이프 라인을 시작할 수 있도록 완벽하게 가능하고 합법적이다 < file command1 .... I / O 재 지정 연산자의 일반적인 위치는 명령 이름과 해당 인수 뒤에 있지만 이는 규칙 일 뿐이며 필수 배치는 아닙니다. 은 <파일 이름 앞에 할 필요가 없습니다. 따라서 >output<input리디렉션 사이에는 완벽한 대칭에 가깝습니다 <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler

15
사람들이 (나를 포함하여) UUoC 돌을 던지는 한 가지 이유는 주로 교육 때문이라고 생각합니다. 때때로 사람들은 파이프를 최소화하는 (UUoC, 순차적 인 greps을 하나로 축소, aso) 기가 바이트의 거대한 텍스트 파일을 처리하며 종종 OP가 실제로 작은 조정이있을 수 있다는 것을 알지 못한다는 질문을 기반으로 안전하게 가정 할 수 있습니다. 엄청난 성능 영향. 나는 뇌주기에 대한 당신의 요점에 전적으로 동의하며 이것이 필요하지 않을 때에도 정기적으로 고양이를 사용하는 이유입니다. 그러나 그것이 필요하지 않다는 것을 아는 것이 중요 합니다.
Adrian Frühwirth

13
이해해주십시오. 나는 그것이 cat쓸모 없다고 말하는 것이 아닙니다 . 그것은 cat쓸모없는 것이 아닙니다 . 특정 구조는 cat. 당신이 메모를 좋아한다면 그것은이다 UUoC (의 쓸모없는 사용 cat), 그리고 하지 UoUC (쓸모의 사용 cat). cat올바른 도구를 사용하는 경우가 많습니다 . 사용하기에 올바른 도구 일 때 사용하는 데 문제가 없습니다 (실제로 내 대답에 사례를 언급하십시오).
Jonathan Leffler 2013 년

6
@randomstring 듣고 있지만 실제로 사용 사례에 달려 있다고 생각합니다. 명령 줄에서 사용할 때 cat파이프에 추가 된 하나 는 데이터에 따라 큰 문제 아닐 수 있지만 프로그래밍 환경으로 사용할 때는 이러한 성능에 중요한 것들을 구현하는 것이 절대적으로 필요할 수 있습니다. 특히 bash성능면에서 직사각형 모양의 바퀴와 같은 것을 다룰 때 ( ksh어쨌든 비교 하면 10 배 더 느리게 말하고 있습니다-농담이 아닙니다). 당신은 큰 스크립트 나 거대한 루프를 처리 할 때 포크 (그리고 단지)를 최적화합니다.
Adrian Frühwirth

58

아니!

우선, 명령에서 리디렉션이 발생하는 위치는 중요하지 않습니다. 따라서 명령 왼쪽에 리디렉션이 마음에 들면 괜찮습니다.

< somefile command

와 같다

command < somefile

둘째, 파이프를 사용할 때 n + 1 프로세스와 서브 쉘이 발생합니다. 확실히 느립니다. 어떤 경우에는 n 이 0이되었을 것입니다 (예를 들어 쉘 내장으로 리디렉션 할 때). 따라서를 사용 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)

더 많은 예제를 추가하려면 자유롭게 편집하십시오.


2
음, 속도 증가는 그리 많지 않을 것입니다.
Dakkaron 2015-08-13

9
"명령"앞에 "<somefile"을 배치하면 기술적으로 왼쪽에서 오른쪽으로 표시되지만 구문 적 경계가 없기 때문에 읽기가 모호 < cat grep dog합니다. 입력 파일과 명령간에 쉽게 구분할 수 없음을 보여주는 인위적인 예입니다. 입력 및 명령에 대한 인수를 수신합니다.
네크로맨서

2
나는 STDIN 리디렉션가 어디로 결정을 채용 한 엄지 손가락의 규칙은 최소화 무엇이든 할 것입니다 외관 모호함 /의 잠재력 에 대한 깜짝합니다. 독단적으로 앞서 간다는 말은 네크로맨서의 문제를 불러 일으키지 만 독단적으로는 뒤 간다고 말하면 똑같은 일을 할 수 있습니다 . 고려 : stdout=$(foo bar -exec baz <qux | ENV=VAR quux). Q.합니까는 <qux적용 foo, 또는에 baz-exec에 의해 D ' foo? A.에 적용 foo되지만 모호하게 보일 수 있습니다 . 이 경우 <qux 앞에 넣는 foo것은 덜 일반적이지만 더 명확하며 후행과 유사합니다 ENV=VAR quux.
마크 G.

3
@necromancer <"cat" grep dog는 읽기가 더 쉽습니다. (저는 보통 프로 공백이지만이 특별한 경우는 매우 예외입니다.)
Charles Duffy

1
@kojiro "가장 확실히 느립니다." 숫자를 뒷받침하지 않고서는 쓸 수 없습니다. 내 번호는 여기에 있습니다 : oletange.blogspot.com/2013/10/useless-use-of-cat.html (그리고 높은 처리량이있을 때만 느리다는 것을 보여줍니다) 귀하의 번호는 어디에 있습니까?
Ole Tange

30

다른 사람을 가르 칠 때 cat문제 나 작업에 적합한 출력을 생성하는 명령의 까다 롭고 복잡한 파이프 라인을 다른 사람에게 가르 칠 때 편리한 자리 표시 자 이기 때문에 과도하게 잘난 UUOC 어워드의 대부분의 경우에 동의하지 않습니다.

이는 특히 Stack Overflow, ServerFault, Unix 및 Linux 또는 모든 SE 사이트와 같은 사이트에서 해당됩니다.

누군가 최적화에 대해 구체적으로 물어 보거나 이에 대한 추가 정보를 추가하고 싶은 경우 고양이를 사용하는 것이 비효율적 인 방법에 대해 이야기하십시오. 그러나 사람들이보기보다 단순함과 이해의 용이성을 목표로 선택했기 때문에 사람들을 꾸짖지 마십시오! 복잡성.

요컨대, 고양이가 항상 고양이는 아니기 때문입니다.

또한 UUOC를 수여하는 것을 즐기는 대부분의 사람들은 사람들을 돕거나 가르치는 것보다 자신이 얼마나 '영리한지'를 과시하는 데 더 관심이 있기 때문에 그렇게합니다. 실제로, 그들은 동료를 이길 작은 막대기를 찾은 또 다른 초보자 일 것입니다.


최신 정보

https://unix.stackexchange.com/a/301194/7696에 답변에 게시 한 또 다른 UUOC가 있습니다 .

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "$1" == "--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 pedants는 $filter빈 문자열 을 기본값으로 설정하고 if명령문 이 다음 을 수행 하도록 쉽게 할 수 있기 때문에 UUOC라고 말할 것입니다 .filter='| grep -v "^$"' IMO에 파이프 문자를 삽입하지 않음으로써 가능하기 때문입니다 $filter.이 "쓸모없는" cat은 사실을 자체 문서화하는 매우 유용한 목적을 제공합니다. 이 라인은 $filter에 대한 printf또 다른 인수가 아니라 sqlplus사용자가 선택할 수있는 선택적 출력 필터입니다.

여러 옵션 출력 필터를 할 필요가 있다면, 옵션 처리는 APPEND 수 | whatever$filter자주 필요 - 하나 개의 여분의 cat파이프 라인은 상처 아무것도에가는 또는 성능에 띄는 손실이 발생하지 않습니다.


11
제쳐두고- ==내부 [ ]는 POSIX에 의해 지정되지 않으며 모든 구현이 그것을 받아들이는 것은 아닙니다. 표준화 된 연산자는 =.
Charles Duffy

27

UUoC 버전에서는 cat파일을 메모리로 읽어 들인 다음 파이프에 써야하며 명령은 파이프에서 데이터를 읽어야하므로 커널이 전체 파일 3 을 복사해야합니다. 번 해야하는 반면 리디렉션 된 경우에는 커널은 파일을 한 번만 복사하면됩니다. 세 번하는 것보다 한 번하는 것이 더 빠릅니다.

사용 :

cat "$@" | command

는 완전히 다르며 cat. 명령이 0 개 이상의 파일 이름 인수를 받아들이고 차례로 처리하는 표준 필터 인 경우에는 여전히 쓸모가 없습니다. tr명령을 고려하십시오 . 파일 이름 인수를 무시하거나 거부하는 순수 필터입니다. 여러 파일을 피드하려면 cat표시된대로 사용해야 합니다. (물론의 설계 tr가별로 좋지 않다는 별도의 논의가 있습니다. 표준 필터로 설계 할 수 없었던 실제 이유는 없습니다.) 명령이 모든 입력을 다음과 같이 처리하도록하려는 경우에도 유효 할 수 있습니다. 명령이 여러 개의 개별 파일을 허용하더라도 여러 개의 개별 파일이 아닌 단일 파일입니다. 예를 들어, wc이러한 명령입니다.

그것은이다 cat single-file무조건 쓸모없는 경우.


26

에서 방어 고양이 :

예,

   < 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 

작동하지 않습니다. 간단한 방법은 오류 발생 가능성이 적고 인체 공학적 명령 catenation은 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을 피하는 것이 좋습니다. 대부분의 질문에 고양이의 사용은 직교 적이며 주제에서 벗어난 것이지 문제가 아닙니다.

매 두 번째 셸 주제에 대해 쓸데없는 쓸모없는 고양이 토론을 시작하는 것은 성 가시고 지루할뿐입니다. 성능 문제를 다룰 때 인생을 얻고 명성의 순간을 기다리십시오.


5
+11111 .. 현재 받아 들여지는 답변의 저자 로서이 유쾌한 보완을 적극 권장합니다. 구체적인 예는 저의 추상적이고 장황한 주장을 설명하고 있으며, 저자의 초기 떨림에서 얻은 웃음 file > a.sh은 혼자서이 글을 읽을 가치가 있습니다 :) 공유해 주셔서 감사합니다!
네크로맨서

이 호출 cat file | wc -c에서 wcEOF까지 stdin을 읽고 바이트를 계산해야합니다. 그러나 이것에서 wc -c < file, 그것은 단지 stdin을 통계하고 그것이 일반 파일임을 알아 내고 입력을 읽는 대신 st_size를 인쇄합니다. 대용량 파일의 경우 성능 차이가 분명하게 나타납니다.
oguz ismail

18

추가 문제는 파이프가 서브 쉘을 조용히 마스킹 할 수 있다는 것입니다. 이 예를 들어, 내가 대체거야 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의 서브 쉘.


5
엄격하게 POSIX 쉘에서는 파이프를 피하기위한 문자열이나 프로세스 대체가 없기 때문에 이것은 까다로운 문제가 될 수 있습니다. BashFAQ 24 에는이 경우에도 몇 가지 유용한 솔루션이 있습니다.
kojiro

4
에서 일부 껍질, 도시 파이프는 서브 쉘을 만들지 않습니다. 예로는 Korn 및 Z가 있습니다. 또한 프로세스 대체 및 here 문자열도 지원합니다. 물론 그들은 엄격하게 POSIX 가 아닙니다 . Bash 4는 shopt -s lastpipe서브 쉘 생성을 피해야합니다.
추후 공지가있을 때까지 일시 중지되었습니다.

13

이것을 정기적으로 지적하는 사람과 다른 여러 쉘 프로그래밍 안티 패턴을 지적하는 사람으로서, 나는 뒤늦게도 무게를 잴 의무가 있다고 느낍니다.

쉘 스크립트는 복사 / 붙여 넣기 언어입니다. 쉘 스크립트를 작성하는 대부분의 사람들은 언어를 배우기위한 것이 아닙니다. 그것은 그들이 실제로 어느 정도 익숙한 언어로 일을 계속하기 위해 극복해야하는 장애물 일뿐입니다.

그런 맥락에서 다양한 쉘 스크립팅 안티 패턴을 전파하는 것이 파괴적이고 잠재적으로 파괴적이라고 생각합니다. 누군가가 Stack Overflow에서 찾은 코드는 최소한의 변경과 불완전한 이해로 환경에 복사 / 붙여 넣기가 가능해야합니다.

넷상의 많은 셸 스크립팅 리소스 중에서 Stack Overflow는 사용자가 사이트의 질문과 답변을 편집하여 사이트의 품질을 형성하는 데 도움을 줄 수 있다는 점에서 특이합니다. 그러나 코드 작성자가 의도하지 않은 변경 작업을 수행하기 쉽기 때문에 코드 편집이 문제가 될 수 있습니다 . 따라서 우리는 코드 변경을 제안하기 위해 주석을 남기는 경향이 있습니다.

UUCA 및 관련 반 패턴 주석은 우리가 주석을다는 코드 작성자만을위한 것이 아닙니다. 그들은 많이 있습니다 주의의 위험 부담 도움말에 독자 들은 여기서 찾을 코드에서 문제를 인식하게 사이트.

Stack Overflow에 대한 답변이 쓸모없는 cats (또는 인용되지 않은 변수, 또는 chmod 777매우 다양한 기타 반 패턴 전염병)를 권장하지 않는 상황을 달성하고 싶지는 않지만 최소한 복사하려는 사용자를 교육 할 수 있습니다. 이 코드를 수백만 번 실행되는 스크립트의 가장 꽉 찬 루프에 붙여 넣습니다.

기술적 이유에 관한 한, 전통적인 통념은 우리가 외부 프로세스의 수를 최소화하려고 노력해야한다는 것입니다. 이것은 쉘 스크립트를 작성할 때 좋은 일반 지침으로 계속 유지됩니다.


2
또한 대용량 파일의 경우 파이핑을 통해 cat많은 추가 컨텍스트 전환 및 메모리 대역폭이 발생합니다 (그리고 cat의 읽기 버퍼 및 파이프 버퍼 에있는 데이터의 추가 복사본으로 인한 L3 캐시 오염 ). 특히 대형 멀티 코어 머신 (많은 호스팅 설정과 같은)에서 캐시 / 메모리 대역폭은 공유 리소스입니다.
Peter Cordes 2018 년

1
@PeterCordes 측정 값을 게시하십시오. 그래서 우리는 그것이 실제로 중요하다면 할 수 있습니다. 내 경험은 일반적으로 문제가되지 않는다는 것입니다 : oletange.blogspot.com/2013/10/useless-use-of-cat.html
올레 단게

1
자신의 블로그는 높은 처리량에 대한 50 % 속도 저하를 보여 주며 총 처리량에 미치는 영향도보고 있지 않습니다 (다른 코어를 바쁘게 유지하는 작업이있는 경우). 내가 그것에 대해 살펴보면 x264 또는 x265가 모든 코어를 사용하여 비디오를 인코딩하는 동안 테스트를 실행하고 비디오 인코딩 속도가 얼마나 느려지는지 확인할 수 있습니다. bzip2그리고 gzip압축은 오버 헤드의 양에 비해 매우 느립니다 cat(그렇지 않으면 머신이 유휴 상태 일 때). 표를 읽기가 어렵습니다 (숫자 중간에 줄 바꿈이 있습니까?). sys시간이 많이 증가하지만 사용자 또는 실제보다 여전히 작습니까?
Peter Cordes

8

나는 종종 cat file | myprogram예에서 사용 합니다. 때때로 나는 고양이의 쓸모없는 사용으로 비난 받고 있습니다 ( http://porkmail.org/era/unix/award.html ). 다음과 같은 이유로 동의하지 않습니다.

  • 무슨 일이 일어나고 있는지 이해하기 쉽습니다.

    UNIX 명령을 읽을 때 명령과 인수, 리디렉션이 예상됩니다. 어디에서나 리디렉션을 넣을 수 있지만 거의 보이지 않으므로 사람들은 예제를 읽는 데 더 어려움을 겪을 것입니다. 나는 믿는다

    cat foo | program1 -o option -b option | program2

    보다 읽기 쉽다

    program1 -o option -b option < foo | program2

    리디렉션을 처음으로 이동하면이 구문에 익숙하지 않은 사람들을 혼란스럽게합니다.

    < foo program1 -o option -b option | program2

    예는 이해하기 쉬워야합니다.

  • 변경하기 쉽습니다.

    프로그램이에서 읽을 수 있다는 것을 알고 있으면 cat일반적으로 STDOUT으로 출력하는 모든 프로그램의 출력을 읽을 수 있다고 가정 할 수 있으므로 필요에 맞게 조정하고 예측 가능한 결과를 얻을 수 있습니다.

  • STDIN이 파일이 아닌 경우 프로그램이 실패하지 않는다는 점을 강조합니다.

    program1 < foo작동 하면 작동 할 것이라고 가정하는 것은 안전하지 않습니다 cat foo | program1. 그러나 그 반대를 가정하는 것이 안전합니다. 이 프로그램은 STDIN이 파일이면 작동하지만 입력이 파이프이면 실패합니다.

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    
    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

성능 비용

추가를 수행하는 데 드는 비용이 있습니다 cat. 기준선 ( cat), 낮은 처리량 ( bzip2), 중간 처리량 ( gzip) 및 높은 처리량 ( grep) 을 시뮬레이션하기 위해 몇 가지 테스트를 얼마나 많이 실행했는지 알 수 있습니다.

cat $ISO | cat
< $ISO cat
cat $ISO | bzip2
< $ISO | bzip2
cat $ISO | gzip
< $ISO gzip
cat $ISO | grep no_such_string
< $ISO grep no_such_string

테스트는 로우 엔드 시스템 (0.6GHz)과 일반 노트북 (2.2GHz)에서 실행되었습니다. 각 시스템에서 10 번 실행되었으며 각 테스트의 최적 상황을 모방하기 위해 최적의 타이밍이 선택되었습니다. $ ISO는 ubuntu-11.04-desktop-i386.iso입니다. (예쁜 테이블 : http://oletange.blogspot.com/2013/10/useless-use-of-cat.html )

CPU                       0.6 GHz ARM
Command                   cat $ISO|                        <$ISO                            Diff                             Diff (pct)
Throughput \ Time (ms)    User       Sys        Real       User       Sys        Real       User       Sys        Real       User       Sys        Real
Baseline (cat)                     55      14453      33090         23       6937      33126         32       7516        -36        239        208         99
Low (bzip2)                   1945148      16094    1973754    1941727       5664    1959982       3420      10430      13772        100        284        100
Medium (gzip)                  413914      13383     431812     407016       5477     416760       6898       7906      15052        101        244        103
High (grep no_such_string)      80656      15133      99049      79180       4336      86885       1476      10797      12164        101        349        114

CPU                       Core i7 2.2 GHz
Command                   cat $ISO|           <$ISO             Diff          Diff (pct)
Throughput \ Time (ms)    User     Sys Real   User   Sys Real   User Sys Real User       Sys Real
Baseline (cat)                    0 356    215      1  84     88    0 272  127          0 423  244
Low (bzip2)                  136184 896 136765 136728 160 137131 -545 736 -366         99 560   99
Medium (gzip)                 26564 788  26791  26332 108  26492  232 680  298        100 729  101
High (grep no_such_string)      264 392    483    216  84    304   48 308  179        122 466  158

결과는 처리량이 낮거나 중간 인 경우 비용이 1 % 정도임을 보여줍니다. 이것은 측정의 불확실성 범위 내에 있으므로 실제로 차이가 없습니다.

높은 처리량의 경우 차이가 더 크고 둘 사이에 분명한 차이가 있습니다.

그 결과 다음 <과 같은 cat |경우 대신 사용해야 합니다.

  • 처리의 복잡성은 단순한 grep과 유사합니다.
  • 성능은 가독성보다 중요합니다.

그렇지 않으면 <또는 을 사용하는지 여부는 중요하지 않습니다 cat |.

따라서 다음과 같은 경우에만 UUoC 보상을 제공해야합니다.

  • 성과의 상당한 차이를 측정 할 수 있습니다 (상을 수여 할 때 측정치를 게시)
  • 성능은 가독성보다 중요합니다.

-3

나는 (전통적인 방법) 파이프를 사용하는 것이 조금 더 빠르다고 생각합니다. 내 상자에서 나는 strace무슨 일이 일어나고 있는지 확인하기 위해 명령을 사용 했습니다.

파이프없이 :

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, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 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, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 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

좋은 벤치마킹을 위해 점점 더 긴 명령 으로 stracetime명령을 사용하여 테스트를 수행 할 수 있습니다 .


9
나는 당신이 pipe를 사용 하는 것 (전통적인 방법) 이 무엇을 의미하는지 , 또는 이것이 strace그것이 더 빠르다고 생각하는 이유를 이해하지 못합니다 . 두 번째 경우에서 실행을 strace추적하지 않습니다 wc -l. 여기에서는 파이프 라인의 첫 번째 명령 만 추적합니다.
kojiro

@kojiro : 나는 전통적인 방법 = 가장 많이 사용되는 방법을 의미합니다 (간접보다 파이프를 더 많이 사용한다고 생각합니다), 더 빠르거나 아닌지 확인할 수 없습니다. 내 추적에서 간접적 인 시스템 호출을 더 많이 보았습니다. ac 프로그램과 루프를 사용하여 더 많은 시간을 소비하는 것을 볼 수 있습니다. 관심이 있으시면 여기에 넣을 수 있습니다 :)
TOC

3
사과 - 투 - 사과 비교는 둘 것입니다 strace -f sh -c 'wc -l < wrong_output.c'함께 strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy 2017

5
다음은 ideone.com의 결과입니다. 현재 없이는 분명히 찬성합니다 cat. ideone.com/2w1W42#stderr
tripleee

1
@CharlesDuffy : 명명 된 파이프를 mkfifo만듭니다 . 익명 파이프가 설정되고 분기되고 부모와 자식이 파이프의 서로 다른 끝을 닫습니다. 하지만 그래,이 답변이 총 말도, 심지어 시스템 호출을 계산하는 시도하거나 사용하지 않은 오버 헤드를 측정하기 위해, 또는 마지막에 호출 할 때마다 상대적으로 타임 스탬프 ...pipe(2)strace -O-r
피터 코르
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.