왜 쉘 루프를 사용하여 텍스트를 처리하는 것이 좋지 않은 것으로 간주됩니까? , 가독성 , 성능 및 보안을 . 이

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

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

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

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

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

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



답변

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

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


답변

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

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


답변

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

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

첫 번째 부분 ( 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명령 수와 정렬 할 파일의 크기 를 최소화하십시오.
  • 가능한 경우 단일 파이프 라인이 가장 효율적입니다. 중간 파일을 사용하여 여러 파이프 라인을 순서대로 실행하면 더 읽기 쉽고 디버그가 가능하지만 프로그램에 걸리는 시간이 늘어납니다.

답변

네,하지만…

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

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

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

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

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

테스트 및 비교 …

물론이야

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

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

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

내 의미 :

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

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

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

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