디렉토리에서 천만 개가 넘는 파일에서 sed를 실행하는 방법은 무엇입니까?


16

10144911 파일이 들어있는 디렉토리가 있습니다. 지금까지 나는 다음을 시도했다.

  • for f in ls; do sed -i -e 's/blah/blee/g' $f; done

내 껍질 ls이 부서졌고, tilda에 있지만 껍질 을 만드는 방법을 알 수 없습니다.

  • ls | xargs -0 sed -i -e 's/blah/blee/g'

에 대한 너무 많은 인수 sed

  • find . -name "*.txt" -exec sed -i -e 's/blah/blee/g' {} \;

더 이상 메모리를 포크 할 수 없습니다

이런 종류의 명령을 만드는 방법에 대한 다른 아이디어가 있습니까? 파일은 서로 통신 할 필요가 없습니다. ls | wc -l작동하는 것 같습니다 (매우 느리므로) 가능해야합니다.


1
sed각 파일에 대한 호출 을 피할 수 있으면 더 빠릅니다 . 일련의 파일을 열고, 편집하고, 저장하고 닫는 방법이 있는지 확실하지 않습니다 sed. 속도가 필수적이라면 펄이나 파이썬과 같은 다른 프로그램을 사용하는 것이 좋습니다.
intuited

@intuited : 파일에 아무것도하지 않는 것이 더 빠를 것입니다 ... 심각하게? 파일 세트에서 패턴을 변경하려면 패턴이 있는지 확인하기 위해 각 파일을 조사해야합니다. '일부'파일을 건너 뛸 수 있다는 것을 미리 알고 있다면 파일을 건드리지 않는 것이 훨씬 빠릅니다. 해당 인터프리터에서 모든 작업을 수행 하는 경우를 제외하고 시작 시간 sed이 시작 python또는 실행보다 빠를 perl수 있습니다 .
akira

@ akira : 명령 줄에 맞는만큼 많은 파일에 대해 perl 또는 python을 한 번 실행하면 해당 파일 각각에 대해 sed를 한 번 실행하는 것보다 비용이 많이 든다는 말입니까? 그것이 사실이라면 정말 놀랐습니다. —————— 내 제안이 편집 프로그램을 한 번만 호출 (시작) (또는 적어도 적은 횟수-내 대답 참조)하고 각 파일을 열고 수정하고 다시 저장 하는 것이 내 제안이라고 이해하지 못했다고 생각 합니다. 각 파일에 대해 개별적으로 편집 프로그램을 호출하는 것이 아니라
11:07에 intuited

첫 번째 의견에는 "python / perl로 sed 교체"를 수행 한 내용이 반영되어 있지 않습니다. .. 명령 줄 OP가 제공 한 @ 명령 줄을 보면 무고한 독자가 "find. -exec python"이라고 가정 할 수 있습니다. "find. -exec sed"보다 빠릅니다.. 분명히 그렇지 않습니다. 자신의 대답에서 실제로 필요한 것보다 훨씬 자주 파이썬을 호출합니다.
akira

아키라는 당신의 (의도 된) 제안을 잘못 해석했다고 생각합니다. 파일 묶기를 제안한다고 생각합니다. 나는 내 xargs 시도, 그것을 다시 시도 할 시간으로 그것을 시도했다 :)
Sandro

답변:


19

이것을 시도하십시오 :

find -name '*.txt' -print0 | xargs -0 -I {} -P 0 sed -i -e 's/blah/blee/g' {}

호출 할 때마다 하나의 파일 이름 만 제공 sed합니다. 이렇게하면 "sed에 너무 많은 인수"문제가 해결됩니다. 이 -P옵션을 사용하면 여러 프로세스를 동시에 분기 할 수 있습니다. 0이 작동하지 않는 경우 (가능한 한 많이 실행해야 함) 다른 숫자 (10? 100? 코어 수?)를 사용하여 숫자를 제한하십시오.


3
아마도 find . -name \*.txt -print0쉘이 glob를 확장하지 않고 1000 만 개의 인수를 찾기 위해 공간을 할당하려고 시도하지 않아야 할 입니다.
Chris Johnsen

@ChrisJohnsen : 그렇습니다. 나는 답을 올리려고 서두르고 그 필수 부분을 포함하지 않았다. 그 수정 사항으로 답변을 편집했습니다. 감사.
추후 공지가있을 때까지 일시 중지되었습니다.

지금 시도 중 ... 손가락 교차
Sandro

7

나는이 메서드 (그리고 다른 모든) 테스트했습니다 (10) 만 달러 "00000001 안녕하세요" "안녕하세요 10000000"(이름 당 14 바이트)로 명명 된 (비어 있음) 파일을.

업데이트 : 이제 메소드 에 쿼드 코어 실행이 포함되었습니다 'find |xargs'(여전히 'sed'없이 echo> / dev / null).

# Step 1. Build an array for 10 million files
#   * RAM usage approx:  1.5 GiB 
#   * Elapsed Time:  2 min 29 sec 
  names=( hello\ * )

# Step 2. Process the array.
#   * Elapsed Time:  7 min 43 sec
  for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done  

다음은 위에서 언급 한 테스트 데이터에 대해 실행될 때 제공된 답변이 어떻게 사라 졌는지 요약 한 것입니다. 이 결과에는 기본 오버 헤드 만 포함됩니다. 즉 'sed'는 호출되지 않았습니다. sed 프로세스는 거의 확실히 시간이 많이 걸리지 만 베어 방법이 어떻게 비교되는지 보는 것이 흥미로울 것이라고 생각했습니다.

'find |xargs'단일 코어를 사용하는 Dennis의 방법 bash arrayno sed달리기 방법 보다 * 4 시간 21 분 ** 더 오래 걸렸습니다 . 그러나 'find'가 제공하는 멀티 코어 이점은 sed가 요구 될 때 나타나는 시간 차이보다 중요합니다. 파일 처리 중 ...

           | Time    | RAM GiB | Per loop action(s). / The command line. / Notes
-----------+---------+---------+----------------------------------------------------- 
Dennis     | 271 min | 1.7 GiB | * echo FILENAME >/dev/null
Williamson   cores: 1x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} echo >/dev/null {}
                               | Note: I'm very surprised at how long this took to run the 10 million file gauntlet
                               |       It started processing almost immediately (because of xargs I suppose),  
                               |       but it runs **significantly slower** than the only other working answer  
                               |       (again, probably because of xargs) , but if the multi-core feature works  
                               |       and I would think that it does, then it could make up the defecit in a 'sed' run.   
           |  76 min | 1.7 GiB | * echo FILENAME >/dev/null
             cores: 4x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} -P 0 echo >/dev/null {}
                               |  
-----------+---------+---------+----------------------------------------------------- 
fred.bear  | 10m 12s | 1.5 GiB | * echo FILENAME >/dev/null
                               | $ time names=( hello\ * ) ; time for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done
-----------+---------+---------+----------------------------------------------------- 
l0b0       | ?@#!!#  | 1.7 GiB | * echo FILENAME >/dev/null 
                               | $ time  while IFS= read -rd $'\0' path ; do echo "$path" >/dev/null ; done < <( find "$HOME/junkd" -type f -print0 )
                               | Note: It started processing filenames after 7 minutes.. at this point it  
                               |       started lots of disk thrashing.  'find' was using a lot of memory, 
                               |       but in its basic form, there was no obvious advantage... 
                               |       I pulled the plug after 20 minutes.. (my poor disk drive :(
-----------+---------+---------+----------------------------------------------------- 
intuited   | ?@#!!#  |         | * print line (to see when it actually starts processing, but it never got there!)
                               | $ ls -f hello * | xargs python -c '
                               |   import fileinput
                               |   for line in fileinput.input(inplace=True):
                               |       print line ' 
                               | Note: It failed at 11 min and approx 0.9 Gib
                               |       ERROR message: bash: /bin/ls: Argument list too long  
-----------+---------+---------+----------------------------------------------------- 
Reuben L.  | ?@#!!#  |         | * One var assignment per file
                               | $ ls | while read file; do x="$file" ; done 
                               | Note: It bombed out after 6min 44sec and approx 0.8 GiB
                               |       ERROR message: ls: memory exhausted
-----------+---------+---------+----------------------------------------------------- 

2

완전히 안전한 찾기를 위한 또 다른 기회 :

while IFS= read -rd $'\0' path
do
    file_path="$(readlink -fn -- "$path"; echo x)"
    file_path="${file_path%x}"
    sed -i -e 's/blah/blee/g' -- "$file_path"
done < <( find "$absolute_dir_path" -type f -print0 )

1

이것은 주로 주제와 관련이 없지만 사용할 수 있습니다.

find -maxdepth 1 -type f -name '*.txt' | xargs python -c '
import fileinput
for line in fileinput.input(inplace=True):
    print line.replace("blah", "blee"),
'

여기서 (이상 ... xargs ... -I {} ... sed ...) 의 주요 이점 은 속도입니다. sed1000 만 번 호출하지 마십시오 . 파이썬이 상대적으로 느리기 때문에 파이썬 사용을 피할 수 있다면 여전히 더 빠를 것이므로 perl 이이 작업에 더 나은 선택 일 수 있습니다. 펄을 사용하여 동등하게 편리하게 수행하는 방법을 모르겠습니다.

이것이 작동하는 방식 xargs은 단일 명령 줄에 맞는만큼 많은 인수로 Python을 호출하고 인수가 부족할 때까지 계속합니다 (에서 제공 ls -f *.txt). 각 호출에 대한 인수의 수는 파일 이름의 길이와 다른 것들에 따라 다릅니다. 이 fileinput.input함수는 각 호출의 인수에 이름이 지정된 파일에서 연속적인 행을 생성하며, inplace옵션은 출력을 마술처럼 "잡아서"각 행을 바꾸는 데 사용하도록 지시합니다.

파이썬의 문자열 replace메소드는 정규 표현식을 사용하지 않습니다. 필요한 경우을 import re사용해야 print re.sub(line, "blah", "blee")합니다. 그것들은 Perl과 호환되는 RegExps입니다 sed -r.

편집하다

주석에서 akira가 언급했듯이, glob ( ls -f *.txt)를 사용하는 원래 버전은 glob find( bash) 자체에 의해 처리되므로 명령 대신에 glob ( )를 사용 합니다. 이것은 명령이 실행되기 전에 천만 개의 파일 이름이 명령 행으로 대체됨을 의미합니다. 이것은 명령의 인수 목록의 최대 크기를 초과하도록 보장됩니다. 이것에 xargs --show-limits대한 시스템 특정 정보에 사용할 수 있습니다 .

인수 목록의 최대 크기도 다음과 같이 고려됩니다. xargs ,이 한계에 따라 파이썬의 각 호출에 전달되는 인수 수를 제한합니다. xargs여전히 파이썬을 꽤 많이 호출해야하기 때문에 os.path.walk파일 목록을 얻는 데 사용 하는 akira의 제안 은 아마도 시간을 절약 할 것입니다.


1
glob 연산자를 사용하는 요점은 os.path.walk()무엇입니까 ( 어쨌든 많은 파일에서 실패 할 것입니다 ...).
akira

@akira : glob 연산자는 .and 의 내용을 바꾸려고하지 ..않습니다. 확실히 다른 방법이 find있지만 (즉 ) OP가 이해하는 것에 최대한 가깝게 노력하고 있습니다. 이것도 사용하지 않는 이유입니다 os.path.walk.
intuited

@ akira : 그러나 좋은 제안은 아마 훨씬 빠를 것입니다.
intuited

나는 OP가 os.path.walk아주 쉽게 이해할 것이라고 생각합니다 .
akira

0

시험:

ls | while read file; do (something to $file); done

2
ls -f더 좋을 것이다. 정말로 기다렸다가 stat()그 많은 파일을 정렬하고 싶습니까?
geekosaur

지금 나는 노력하고 있습니다 : * .txt의 f; ㅋㅋㅋ 끝난. 내가 실패하면 난처하게 줄 것이다. 감사합니다!
Sandro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.