왜 쉘 루프를 사용하여 텍스트를 처리하는 것이 좋지 않은 것으로 간주됩니까?


196

POSIX 쉘에서 일반적으로 좋지 않은 것으로 간주되는 텍스트를 처리하기 위해 while 루프 를 사용 합니까?

으로 스테판 Chazelas가 지적 , 쉘 루프를 사용하지 않는 이유 중 일부입니다 개념 , 신뢰성 , 가독성 , 성능보안을 .

답변신뢰성가독성 측면을 설명합니다 .

while IFS= read -r line <&3; do
  printf '%s\n' "$line"
done 3< "$InputFile"

의 경우 성능while루프와 읽기는 파일 또는 파이프에서 읽을 때 때문에, 대단히 느린 읽기 쉘 내장은 한 번에 하나 개의 문자를 읽습니다.

방법에 대한 개념보안 측면?



1
읽기 쉘 내장은 한 번에 한 문자를 읽지 않고 한 번에 한 줄을 읽습니다. wiki.bash-hackers.org/commands/builtin/read
A.Danischewski

@ A.Danischewski : 쉘에 따라 다릅니다. 에서 bash, 그것은 한 번에 하나 개의 버퍼 크기를 읽어보십시오 dash예를 들면. 또한 unix.stackexchange.com/q/209123/38906
cuonglm을

답변:


256

예, 다음과 같은 여러 가지가 있습니다.

while read line; do
  echo $line | cut -c3
done

또는 더 나쁜 :

for line in `cat file`; do
  foo=`echo $line | awk '{print $2}'`
  echo whatever $foo
done

(웃지 마, 나는 그중 많은 것을 보았습니다).

일반적으로 쉘 스크립팅 초보자가 제공합니다. 이것들은 C 나 파이썬과 같은 명령형 언어로 할 것의 순전 한 문자 그대로의 번역이지만 쉘에서하는 일이 아니며, 그 예는 매우 비효율적이며 완전히 신뢰할 수 없으며 (잠재적으로 보안 문제를 일으킬 수 있음) 대부분의 버그를 수정하기 위해 코드를 읽을 수 없게됩니다.

개념적으로

C 또는 대부분의 다른 언어에서 빌딩 블록은 컴퓨터 명령보다 한 단계 위입니다. 프로세서에 수행 할 작업과 다음에 수행 할 작업을 알려줍니다. 당신은 당신의 프로세서를 손으로 잡고 그것을 마이크로 관리합니다 : 당신은 그 파일을 열고, 많은 바이트를 읽습니다.

쉘은 고급 언어입니다. 언어조차도 말할 수 없습니다. 그들은 모든 명령 줄 인터프리터 앞에 있습니다. 작업은 사용자가 실행하는 명령으로 수행되며 셸은 명령을 조정하기위한 것입니다.

유닉스가 도입 한 가장 큰 장점 중 하나는 파이프 와 모든 명령이 기본적으로 처리하는 기본 stdin / stdout / stderr 스트림이었습니다.

45 년 동안, 우리는 명령의 힘을 활용하고 작업에 협력하도록 API보다 나은 것을 찾지 못했습니다. 아마도 오늘날 사람들이 여전히 껍질을 사용하는 주된 이유 일 것입니다.

절단 도구와 음역 도구가 있으며 간단하게 수행 할 수 있습니다.

cut -c4-5 < in | tr a b > out

쉘은 배관 작업을 수행하고 있으며 (파일을 열고 파이프를 설정하고 명령을 호출 함) 모든 준비가 완료되면 쉘이 아무 작업도하지 않고 흐릅니다. 이 도구는 충분한 버퍼링으로 자신의 속도에 맞춰 작업을 동시에 효율적으로 수행하므로, 한 사람이 다른 사람을 차단하지 않고 아름답고 단순합니다.

도구를 호출하는 데는 비용이 들지만 성능 측면에서이를 개발할 것입니다. 이러한 도구는 C로 된 수천 개의 명령으로 작성 될 수 있습니다. 프로세스를 작성하고, 도구를로드, 초기화, 정리, 프로세스 파기 및 대기해야합니다.

호출 cut은 부엌 서랍을 열고 칼을 가지고 사용하고 씻고 말리고 서랍에 다시 넣는 것과 같습니다. 할 때 :

while read line; do
  echo $line | cut -c3
done < file

그것은 파일의 각 줄과 같 read으며 부엌 서랍에서 도구를 가져옵니다 ( 그것에 맞게 설계되지 않았기 때문에 매우 서투른 것 ), 줄을 읽고, 읽은 도구를 씻고, 서랍에 다시 넣으십시오. 그런 다음 도구 echocut도구에 대한 모임을 예약 하고 서랍에서 가져 와서 호출하고 씻고 말리고 서랍에 다시 넣습니다.

이러한 도구 (일부 read하고 echo) 대부분의 쉘에 내장되어 있습니다,하지만 거의 때문에 여기에 차이가 없습니다 echo그리고 cut여전히 별도의 프로세스에서 실행해야합니다.

그것은 양파를 자르는 것과 같지만 칼을 씻고 각 조각 사이의 부엌 서랍에 다시 넣으십시오.

여기서 확실한 방법은 cut도구를 서랍에서 꺼내어 전체 양파를 썰어 작업이 완료된 후 다시 서랍에 넣는 것입니다.

쉘, 특히 텍스트를 처리하기 위해 IOW는 가능한 한 적은 유틸리티를 호출하고 작업에 협력하게하고 다음 도구를 실행하기 전에 각 도구가 시작, 실행, 정리 될 때까지 수천 개의 도구를 순서대로 실행하지 않습니다.

또한 읽기 브루스의 좋은 대답 . 쉘의 하위 수준 텍스트 처리 내부 도구 (의 경우는 제외 zsh)는 제한적이고 번거로우 며 일반적으로 일반 텍스트 처리에 적합하지 않습니다.

공연

앞에서 언급했듯이 하나의 명령을 실행하면 비용이 발생합니다. 해당 명령이 내장되어 있지 않으면 막대한 비용이 들지만 내장되어 있어도 비용이 큽니다.

그리고 쉘은 그런 식으로 실행되도록 설계되지 않았으며, 성능이 뛰어난 프로그래밍 언어가 될 수 없습니다. 그들은 단지 명령 줄 해석 기일뿐입니다. 따라서이 부분에서는 최적화가 거의 이루어지지 않았습니다.

또한 쉘은 별도의 프로세스에서 명령을 실행합니다. 이러한 빌딩 블록은 공통 메모리 또는 상태를 공유하지 않습니다. a fgets()또는 fputs()C 를 수행하면 stdio의 기능입니다. stdio는 모든 stdio 기능에 대한 입력 및 출력을위한 내부 버퍼를 유지하여 값 비싼 시스템 호출을 너무 자주 수행하지 않습니다.

해당 심지어 내장 쉘 유틸리티 ( read, echo, printf) 그렇게 할 수 없습니다. read한 줄을 읽습니다. 줄 바꿈 문자를지나 읽은 경우 다음에 실행하는 명령이 누락됩니다. 따라서 read한 번에 한 바이트 씩 입력을 읽어야합니다 (일부 구현에서는 입력이 일반 파일 인 경우 청크를 읽고 다시 검색하지만 최적화는 일반 파일에서만 작동 bash하며 예를 들어 128 바이트 청크 만 읽습니다) 여전히 텍스트 유틸리티보다 훨씬 적습니다.)

출력 측에서와 마찬가지로 출력을 echo버퍼링 할 수는 없으며 다음 명령을 실행하면 해당 버퍼를 공유하지 않기 때문에 즉시 출력해야합니다.

분명히, 명령을 순차적으로 실행한다는 것은 명령을 기다려야한다는 것을 의미합니다. 쉘과 도구에서 제어 할 수있는 약간의 스케줄러 댄스입니다. 또한 (파이프 라인에서 오래 실행되는 도구 인스턴스를 사용하는 것과는 대조적으로) 사용 가능한 경우 여러 프로세서를 동시에 활용할 수 없다는 것을 의미합니다.

while read루프와 (어쩌면) 동등한 것 사이 cut -c3 < file에서, 나의 빠른 테스트에서, 내 테스트에서 약 40000의 CPU 시간 비율이 있습니다 (1 초 대 반나절). 그러나 쉘 내장 만 사용하더라도 :

while read line; do
  echo ${line:2:1}
done

(여기서는 bash)로, 여전히 약 1 : 600입니다 (1 초 대 10 분).

신뢰성 / 가독성

해당 코드를 올바르게 얻는 것은 매우 어렵습니다. 내가 준 예제는 야생에서 너무 자주 보지만 많은 버그가 있습니다.

read다양한 작업을 수행 할 수있는 편리한 도구입니다. 사용자의 입력을 읽고 단어로 분리하여 다른 변수에 저장할 수 있습니다. read line않습니다 하지 입력 라인을 읽거나 어쩌면 그것은 매우 특별한 방법으로 행을 읽습니다. 실제로 판독 단어를 입력으로부터 분리하여 그 단어 $IFS와 백 슬래시 여기서 세퍼레이터 또는 개행 문자를 탈출하는 데 사용될 수있다.

기본값이 다음 $IFS과 같은 입력에서

   foo\/bar \
baz
biz

read line저장할 "foo/bar baz"$line하지, " foo\/bar \"예상대로.

한 줄을 읽으려면 실제로 다음이 필요합니다.

IFS= read -r line

그것은 매우 직관적이지는 않지만 그렇게 된 것입니다. 포탄은 그런 식으로 사용되지 않았 음을 기억하십시오.

동일합니다 echo. echo시퀀스를 확장합니다. 임의 파일의 내용과 같은 임의의 내용에는 사용할 수 없습니다. printf대신 여기 가 필요 합니다.

물론, 모두가 빠뜨릴 수있는 변수인용하는 것을 잊어 버리는 것이 일반적 입니다. 그래서 더 있습니다 :

while IFS= read -r line; do
  printf '%s\n' "$line" | cut -c3
done < file

이제 몇 가지주의 사항이 있습니다.

  • 를 제외하고 zsh는 적어도 GNU 텍스트 유틸리티에 문제가 없지만 입력에 NUL 문자가 포함되어 있으면 작동하지 않습니다.
  • 마지막 줄 바꿈 뒤에 데이터가 있으면 건너 뜁니다.
  • 루프 내에서 stdin이 리디렉션되므로 stdin에서 명령을 읽지 않도록주의해야합니다.
  • 루프 내의 명령에 대해서는 성공 여부에주의를 기울이지 않습니다. 일반적으로 오류 (디스크 전체, 읽기 오류 ...) 조건은 가난보다 일반적으로 더 가난하게, 처리됩니다 올바른 해당.

위의 문제 중 일부를 해결하려면 다음과 같이됩니다.

while IFS= read -r line <&3; do
  {
    printf '%s\n' "$line" | cut -c3 || exit
  } 3<&-
done 3< file
if [ -n "$line" ]; then
    printf '%s' "$line" | cut -c3 || exit
fi

점점 가독성이 떨어지고 있습니다.

인수를 통해 명령에 데이터를 전달하거나 변수에서 출력을 검색하는 데는 여러 가지 다른 문제가 있습니다.

  • 인수의 크기에 대한 제한
  • NUL 문자 (텍스트 유틸리티에도 문제가 있음)
  • 그들이 함께 시작할 때 인수가 옵션으로 촬영 -(또는 +가끔)
  • 루프에서 일반적으로 사용되는 다양한 명령의 다양한 단점 expr, test...
  • 일관성없는 방식으로 멀티 바이트 문자를 처리하는 다양한 쉘의 (제한된) 텍스트 조작 연산자
  • ...

보안 고려 사항

변수명령에 대한 인수로 작업을 시작 하면 광산 필드를 입력하게됩니다.

변수 인용잊어 버리고 옵션 마커끝을 잊어 버리고 멀티 바이트 문자가있는 로케일 (요즘 규범)에서 작업하면 조만간 버그가 발생할 수 있습니다.

루프를 사용하고 싶을 때.

TBD


24
명확하고 생생하고 읽기 쉽고 도움이됩니다. 다시 한번 감사드립니다. 이것은 실제로 쉘 스크립팅과 프로그래밍의 근본적인 차이점에 대해 인터넷 어디에서나 본 최고의 설명입니다.
와일드 카드

2
이 글은 초보자들이 쉘 스크립트에 대해 배우고 미묘한 차이점을 알 수 있도록 도와줍니다. null을 얻지 않도록 참조 변수를 $ {VAR : -default_value}로 추가해야합니다. 정의되지 않은 값을 참조 할 때 소리를 내도록 -o nounset을 설정하십시오.
unsignedzero

6
@ A.Danischewski, 요점을 놓친 것 같습니다. 예 cut를 들어 효율적입니다. cut -f1 < a-very-big-fileC로 작성하면 얻을 수있는만큼 효율적입니다. 굉장히 비효율적이며 오류가 발생하기 쉬운 것은 쉘 루프 cut의 모든 행에 대해 호출 됩니다 a-very-big-file.이 답변에서 요점입니다. 그것은 불필요한 코드 작성에 대한 마지막 진술과 일치하므로 귀하의 의견을 이해하지 못할 수도 있습니다.
Stéphane Chazelas

5
"45 년 동안 우리는 명령의 힘을 활용하고 작업에 협조하도록 API보다 더 나은 방법을 찾지 못했습니다." 실제로 PowerShell은 바이트 스트림이 아닌 구조화 된 데이터를 전달하여 두려운 파싱 문제를 해결했습니다. 쉘이 아직 그것을 사용하지 않는 유일한 이유는 (이 아이디어는 꽤 오랫동안 존재 해 왔으며 현재 표준 목록 및 사전 컨테이너 유형이 주류가되었을 때 Java를 중심으로 언젠가 결정화되었다는 것) 그들의 관리자가 아직 동의 할 수 없기 때문입니다 사용할 일반적인 구조화 된 데이터 형식 (.
ivan_pozdeev

6
@OlivierDulac 나는 그것이 약간 유머라고 생각합니다. 이 섹션은 영원히 TBD가 될 것입니다.
muru

43

개념과 가독성에 관한 한, 쉘은 일반적으로 파일에 관심이 있습니다. "주소 단위"는 파일이고 "주소"는 파일 이름입니다. 쉘에는 파일 존재, 파일 유형, 파일 이름 형식 (글 로빙으로 시작)을 테스트하는 모든 종류의 방법이 있습니다. 쉘은 파일 내용을 다루기위한 프리미티브가 거의 없습니다. 셸 프로그래머는 파일 내용을 처리하기 위해 다른 프로그램을 호출해야합니다.

파일 및 파일 이름 방향으로 인해 셸에서 텍스트 조작을 수행하는 것은 실제로 느리지 만 명확하지 않고 왜곡 된 프로그래밍 스타일이 필요합니다.


25

복잡한 답변이있어 우리 중 괴짜들에게 흥미로운 세부 정보를 많이 제공하지만 실제로는 매우 간단합니다. 쉘 루프에서 큰 파일을 처리하는 것은 너무 느립니다.

질문자는 일반적인 종류의 셸 스크립트에서 흥미 롭다고 생각합니다.이 스크립트는 주요 작업을 시작하기 전에 명령 줄 구문 분석, 환경 설정, 파일 및 디렉토리 확인 및 조금 더 초기화로 시작할 수 있습니다. 줄 지향적 텍스트 파일.

첫 번째 부분 ( initialization)의 경우 일반적으로 쉘 명령이 느리다는 것은 중요하지 않습니다. 수십 개의 명령 만 실행 중일 수 있습니다. 우리가 그 부분을 비효율적으로 작성하더라도, 모든 초기화를 수행하는 데 보통 1 초도 걸리지 않을 것입니다. 그리고 한 번만 발생합니다.

그러나 수천 줄이나 수백만 줄을 가질 수있는 큰 파일을 처리 할 때 쉘 스크립트가 각 줄에 대해 1 초 (수십 밀리 초 일지라도)의 큰 부분을 차지하는 것은 좋지 않습니다 . 최대 몇 시간이 걸릴 수 있습니다.

그때 우리는 다른 도구를 사용해야 할 때가 있는데, 유닉스 쉘 스크립트의 장점은 우리가 그렇게하기가 매우 쉽다는 것입니다.

루프를 사용하여 각 줄을 보는 대신 명령 파이프 라인을 통해 전체 파일을 전달해야 합니다 . 즉, 명령을 수천 또는 수백만 번 호출하는 대신 쉘은 명령을 한 번만 호출합니다. 이러한 명령에는 파일을 한 줄씩 처리하기위한 루프가 있지만 셸 스크립트가 아니며 빠르고 효율적으로 설계되었습니다.

유닉스에는 파이프 라인을 구축하는 데 사용할 수있는 간단한 도구부터 복잡한 도구까지 다양한 멋진 도구가 내장되어 있습니다. 나는 보통 간단한 것부터 시작하고 필요할 때 더 복잡한 것을 사용합니다.

또한 대부분의 시스템에서 사용할 수있는 표준 도구를 사용하려고 노력하고 항상 가능한 것은 아니지만 사용을 휴대용으로 유지하려고합니다. 그리고 좋아하는 언어가 Python 또는 Ruby 인 경우 소프트웨어를 실행해야하는 모든 플랫폼에 언어를 설치하려는 추가 노력이 필요하지 않을 수 있습니다. :-)

간단한 도구를 포함 head, tail, grep, sort, cut, tr, sed, join(이 개 파일을 병합 할 때), 그리고 awk많은 다른 사람의 사이에서 한 - 라이너. 일부 사람들이 패턴 일치 및 sed명령으로 무엇을 할 수 있는지는 놀랍습니다 .

더 복잡해지고 각 줄에 약간의 논리를 적용해야 할 awk때 좋은 옵션입니다. 한 줄 짜리 라이너 (일부 사람들은 완전히 읽을 수는 없지만 전체 awk 스크립트를 '한 줄'에 넣습니다) 또는 짧은 외부 스크립트.

으로 awk(쉘 등) 해석 된 언어이며, 그것은 매우 효율적으로 라인 별 처리를 할 수있는 놀라운하지만이를 위해 특수 제작 그리고 그것은 정말 빠릅니다.

그리고 Perl텍스트 파일을 처리하는 데 능숙하고 유용한 라이브러리가 많이있는 수많은 다른 스크립팅 언어가 있습니다.

마지막으로, 최대 속도 와 높은 유연성 이 필요한 경우 (구문 처리가 약간 지루하지만) 오래된 C가 있습니다 . 그러나 당신이 만나는 모든 파일 처리 작업에 대해 새로운 C 프로그램을 작성하는 것은 아마도 시간을 잘못 사용하는 것입니다. CSV 파일을 많이 사용하므로 C에서 여러 가지 일반 유틸리티를 작성하여 여러 프로젝트에서 재사용 할 수 있습니다. 실제로, 이것은 쉘 스크립트에서 호출 할 수있는 '단순하고 빠른 유닉스 도구'의 범위를 확장 시키므로 매번 맞춤형 C 코드를 작성하고 디버깅하는 것보다 훨씬 빠른 스크립트 만 작성하면 대부분의 프로젝트를 처리 할 수 ​​있습니다!

마지막 힌트 :

  • 로 메인 쉘 스크립트를 시작하는 것을 잊지 마십시오. 그렇지 않으면 export LANG=C많은 도구가 평범한 ASCII 파일을 유니 코드로 취급하므로 훨씬 느려집니다.
  • 또한 환경에 관계없이 일관된 주문을 export LC_ALL=C하려면 설정을 고려 sort하십시오!
  • sort데이터 가 필요한 경우 다른 모든 것보다 시간과 리소스가 더 많이 소요될 수 있습니다 (CPU, 메모리, 디스크). sort명령 수와 정렬 할 파일의 크기 를 최소화하십시오.
  • 가능한 경우 단일 파이프 라인이 가장 효율적입니다. 중간 파일을 사용하여 여러 파이프 라인을 순서대로 실행하면 더 읽기 쉽고 디버그가 가능하지만 프로그램에 걸리는 시간이 늘어납니다.

6
많은 간단한 도구 (특히 머리, 꼬리, 그렙, 정렬, 절단, tr, sed 등의 언급 된 도구)의 파이프 라인은 종종 불필요하게 사용됩니다. 특히 파이프 라인에 이미 awk 인스턴스가있는 경우 그 간단한 도구의 작업도. 고려해야 할 또 다른 문제는 파이프 라인에서 파이프 라인 앞쪽의 프로세스에서 뒷면에 나타나는 프로세스로 상태 정보를 간단하고 안정적으로 전달할 수 없다는 것입니다. 간단한 프로그램의 파이프 라인에 awk 프로그램을 사용하면 단일 상태 공간이 있습니다.
Janis

14

네,하지만...

스테판 Chazelas가의 정답 을 기반으로 특정 바이너리처럼에 대한 모든 텍스트 작업 위임의 개념 grep, awk, sed등을.

으로 혼자서 많은 일을 수행 할 수, 포기 포크 것은 (심지어 모든 일을하고 또 다른 인터프리터를 실행하는 것보다) 더 빨리 될 수 있습니다.

샘플을 보려면이 게시물을 살펴보십시오.

https://stackoverflow.com/a/38790442/1765658

https://stackoverflow.com/a/7180078/1765658

테스트 및 비교 ...

물론이야

사용자 입력보안 에 대한 고려는 없습니다 !

웹 응용 프로그램을 작성하지 마십시오 !

그러나 대신 사용할 수 있는 많은 서버 관리 작업의 경우 내장 bash를 사용하면 매우 효율적일 수 있습니다.

내 의미 :

bin utils 와 같은 도구를 작성하는 것은 시스템 관리와 ​​같은 종류의 작업이 아닙니다.

그래서 같은 사람들이 아닙니다!

sysadmin 은 알아야하는 곳 에서 선호하는 도구를 사용하여 프로토 타입shell작성할 수 있습니다.

이 새로운 유틸리티 (시제품)가 실제로 유용하다면, 다른 사람들은 좀 더 적절한 언어를 사용하여 전용 도구를 개발할 수 있습니다.


1
좋은 예입니다. 귀하의 접근 방식은 lololux보다 훨씬 효율적이지만 tensibai의 답변 (이 루프를 사용하지 않고 IMO를 수행하는 올바른 방법)이 귀하보다 훨씬 빠릅니다. 를 사용 하지 않으면 훨씬 빠릅니다 bash. (시스템 테스트에서 ksh93을 사용하면 3 배 이상 빠릅니다). bash일반적으로 가장 느린 껍질입니다. 심지어 zsh배 빠른 해당 스크립트에 있습니다. 또한 인용되지 않은 변수와의 사용법에 몇 가지 문제가 read있습니다. 그래서 당신은 실제로 여기 내 요점을 많이 보여주고 있습니다.
Stéphane Chazelas

@ StéphaneChazelas 동의합니다 .bash 는 아마도 오늘날 사람들이 사용할 수 있는 가장 느린 이지만 어쨌든 가장 널리 사용됩니다.
F. Hauri

@ StéphaneChazelas 내 답변 버전을 게시했습니다
F. Hauri

1
@Tensibai, 당신은 발견 할 것이다 POSIXsh , awk는 , Sed의 , grep, ed, ex, cut, sort, join배쉬보다 더 신뢰성 ... 모든 또는 펄.
와일드 카드

1
@Tensibai, U & L과 관련된 모든 시스템 중 대부분 (Solaris, FreeBSD, HP / UX, AIX, 대부분의 임베디드 Linux 시스템 ...)은 bash기본적 으로 설치되어 있지 않습니다 . bash애플 맥 OS와 GNU 시스템에서만 찾을 수있다. (주로 배포본 이라고 생각한다. ) 많은 시스템에도 옵션 패키지 (예 zsh: tcl, python...)가있다
Stéphane Chazelas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.