질문

질문

이 질문은 다음에서 영감을 얻었습니다.

쉘 루프를 사용하여 텍스트를 처리하는 것이 왜 나쁜 습관으로 간주됩니까?

이런 구조가 보이네요

for file in `find . -type f -name ...`; do smth with ${file}; done

그리고

for dir in $(find . -type d -name ...); do smth with ${dir}; done

거의 매일 여기에서 사용됩니다. 어떤 사람들은 이러한 종류의 것들을 피해야 하는 이유를 설명하는 이 게시물에 시간을 할애하지만...
이와 같은 게시물의 수를 보면(때로는 이러한 댓글이 단순히 무시된다는 사실도 있습니다) 질문을 하나 드려도 좋을 것 같습니다.

find루프의 출력이 나쁜 습관인 이유는 무엇입니까 ? 반환된 각 파일 이름/경로에 대해 하나 이상의 명령을 실행하는 올바른 방법은 무엇입니까 find?

답변1

출력 반복이 find나쁜 습관인 이유는 무엇입니까?

간단한 대답은 다음과 같습니다.

파일 이름에는 다음이 포함될 수 있으므로어느특징.

그래서,파일 이름을 구분하는 데 안정적으로 사용할 수 있는 인쇄 가능한 문자가 없습니다.


개행 문자는 다음과 같습니다.자주(부정확하게) 파일 이름을 구분하는 이유는 다음과 같습니다.이상파일 이름에 줄바꿈을 포함합니다.

그러나 임의의 가정을 바탕으로 소프트웨어를 구축하면 기껏해야 예외를 처리할 수 없으며 최악의 경우 시스템 제어권을 상실하는 악의적인 공격에 취약해집니다. 따라서 이는 견고성과 보안의 문제입니다.

두 가지 다른 방법으로 소프트웨어를 작성할 수 있는데, 그 중 하나는 극단적인 경우(비정상적인 입력)를 올바르게 처리했지만 다른 하나는 읽기가 더 쉽다면 이를 절충점으로 생각할 수 있습니다. (그렇지 않습니다. 저는 올바른 코드를 선호합니다.)

그러나 정확하고 강력한 코드 버전이 있는 경우반품읽기 쉽고 극단적인 경우에 실패하는 코드를 작성할 이유가 없습니다. find발견된 모든 파일에 대해 명령을 실행해야 하는 경우입니다 .


좀 더 구체적으로 설명하자면, UNIX 또는 Linux 시스템에서 파일 이름은 /a(경로 구성 요소 구분 기호로 사용됨)를 제외한 모든 문자를 포함할 수 있으며 null 바이트를 포함할 수 없습니다.

따라서 널 바이트는오직파일 이름을 분리하는 올바른 방법.


GNU에는 메인이 find포함되어 있으므로 -print0인쇄하는 파일 이름을 구분하기 위해 널 바이트를 사용합니다. GNUfind 할 수 있는xargs다음 출력을 처리하기 -0위해 GNU 및 해당 플래그(및 플래그)와 함께 안전하게 사용할 수 있습니다 .-rfind

find ... -print0 | xargs -r0 ...

그런데 마땅한 방법이 없군요이유다음과 같은 이유로 이 양식을 사용하십시오.

  1. 존재할 필요가 없는 GNU findutils에 대한 종속성을 추가합니다.
  2. find디자인됨찾은 파일에 대해 명령을 실행하는 기능.

또한 GNU에서는 xargs필수 -0이며 -rFreeBSD에서는 xargs필수만 필요하고 -0(옵션이 없음 -r) 일부에서는 xargs전혀 지원하지 않습니다. 따라서 POSIX 기능 (다음 섹션 참조)을 고수하고 건너뛰는 -0것이 좋습니다 .findxargs

find포인트 2( 찾은 파일에 대해 명령을 실행하는 기능) 에 관해서는 Mike Loukides가 가장 잘 말한 것 같습니다.

find업무는 파일을 찾는 것이 아니라 표현식을 평가하는 것입니다. 예, find물론 파일을 찾을 수 있지만 실제로는 부작용일 뿐입니다.

--유닉스 전동 공구


POSIX 지정 목적find

find각 결과에 대해 하나 이상의 명령을 실행하는 올바른 방법은 무엇입니까?

발견된 각 파일에 대해 단일 명령을 실행하려면 다음을 사용하십시오.

find dirname ... -exec somecommand {} \;

발견된 각 파일에 대해 여러 명령을 순차적으로 실행하려면 첫 번째 명령이 성공한 경우에만 두 번째 명령을 실행하려면 다음을 사용합니다.

find dirname ... -exec somecommand {} \; -exec someothercommand {} \;

여러 파일에 대해 단일 명령을 동시에 실행하려면 다음을 수행하십시오.

find dirname ... -exec somecommand {} +

find결합하다sh

당신이 사용해야하는 경우껍데기출력 리디렉션, 파일 이름에서 확장명 제거 등 명령 내의 기능에 이 sh -c구성을 사용할 수 있습니다. 이에 대해 알아야 할 몇 가지 사항이 있습니다.

  • 안 돼요{}sh코드 에 직접 삽입하세요 . 이를 통해 악의적으로 제작된 파일 이름에서 임의의 코드를 실행할 수 있습니다. 그리고 실제로 POSIX는 작동한다고 명시하지도 않습니다. (다음 항목을 참조하세요.)

  • {}여러 번 사용하거나 긴 인수의 일부로 사용 하지 마세요 . 이것은 휴대용이 아닙니다. 예를 들어 다음과 같이 하지 마세요.

    find ... -exec cp {} somedir/{}.bak \;

    인용하다POSIX 사양find:

    만약유틸리티 이름또는토론문자열에 "{}" 두 문자가 포함되어 있는지 여부는 구현에 따라 정의되지만 "{}" 두 문자만 포함되는 것은 아닙니다.찾다이 두 문자를 바꾸거나 변경되지 않은 문자열을 사용하십시오.

    ...두 문자 "{}"를 포함하는 인수가 여러 개 있는 경우 동작이 지정되지 않습니다.

  • 옵션에 전달된 셸 명령 문자열 뒤에 오는 인수는 -c셸에 대한 위치 인수로 설정됩니다.에서 시작하다$0. 로 시작하지 마십시오 $1.

    따라서 예를 들어 생성된 셸에서 오류를 보고하는 데 사용할 "더미" $0값을 포함하는 것이 좋습니다 . 또한 이를 통해 여러 파일을 셸에 전달할 때 find-sh와 같은 구문을 사용할 수 있습니다 . 반면 생략된 값 은 전달된 첫 번째 파일이 로 설정되므로 에 포함되지 않음을 의미합니다 ."$@"$0$0"$@"


각 파일에 대해 단일 쉘 명령을 실행하려면 다음을 사용하십시오.

find dirname ... -exec sh -c 'somecommandwith "$1"' find-sh {} \;

그러나 일반적으로 발견된 모든 파일에 대해 셸을 생성하지 않도록 셸 루프에서 파일을 처리하는 것이 더 나은 성능을 제공합니다.

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' find-sh {} +

( 각 위치 인수는 for f do동일 for f in "$@"; do하며 차례로 처리됩니다. 즉, find이름에 특수 문자가 있는지 여부에 관계없이 찾은 모든 파일을 사용합니다.)


올바른 사용법 find의 추가 예 :

(참고: 이 목록을 자유롭게 확장하세요.)

답변2

질문

for f in $(find .)

양립할 수 없는 두 가지를 하나로 합치다.

find개행으로 구분된 파일 경로 목록을 인쇄합니다. $(find .)목록 컨텍스트에서 따옴표를 따옴표로 묶지 않은 상태로 두면 분할+glob 연산자가 호출되어 이를 문자(기본적으로 개행 문자 포함, 공백과 탭(및 NUL in ) 포함)로 분할 하고 $IFS모든 항목 zsh에서 globbing 작업(제외)을 수행합니다. 결과 단어 zsh(ksh93의 중괄호 확장( braceexpand이전 버전에서 해당 옵션이 꺼진 경우에도) 또는 pdksh 파생어!).

그렇게 하더라도:

IFS='
' # split on newline only
set -o noglob # disable glob (also disables brace expansion
              # done upon other expansions in ksh)
for f in $(find .) # invoke split+glob

줄 바꿈은 파일 경로의 모든 문자만큼 유효하기 때문에 이는 여전히 잘못된 것입니다. 의 find -print출력그림에서 알 수 있듯이).

이는 또한 쉘이 출력을 완전히 저장 find한 다음 파일에 대한 루프를 시작하기 전에 출력을 분할+글로브해야 함을 의미합니다(출력을 메모리에 다시 저장함을 의미함).

유사한 문제 가 있습니다(공백, 개행, 작은따옴표, 큰따옴표 및 백슬래시( 유효한 문자의 일부를 형성하지 않는 find . | xargs cmd특정 구현 바이트 포함)도 모두 문제입니다).xarg

좀 더 올바른 선택

for출력에서 루프를 사용하는 유일한 방법은 및에 대한 지원을 find사용하는 것입니다 .zshIFS=$'\0'

IFS=$'\0'
for f in $(find . -print0)

( -print0지원하지 않는 비표준 구현으로 대체됨(그러나 현재는 일반적임) -exec printf '%s\0' {} +).find-print0

여기서 정확하고 이식 가능한 방법은 다음을 사용하는 것입니다 -exec.

find . -exec something with {} \;

또는 something여러 매개변수를 사용할 수 있는 경우:

find . -exec something with {} +

쉘에서 파일 목록을 처리해야 하는 경우:

find . -exec sh -c '
  for file do
    something < "$file"
  done' find-sh {} +

(하나 이상 시작할 수도 있습니다 sh.)

일부 시스템에서는 다음을 사용할 수 있습니다.

find . -print0 | xargs -r0 something with

이는 표준 구문에 비해 장점이 거의 없으며 something파이프 stdin또는 /dev/null.

이를 사용하려는 한 가지 이유는 병렬 처리를 위해 -PGNU 옵션을 사용하는 것일 수 있습니다. xargs이 문제는 프로세스 대체를 지원하는 쉘 옵션을 사용하여 stdinGNU를 통해 해결할 수도 있습니다 .xargs-a

xargs -r0n 20 -P 4 -a <(find . -print0) something

something예를 들어 각각 20개의 파일 매개변수를 사용하여 최대 4개의 동시 호출을 실행합니다 .

zsh또는 을 사용하여 bash출력을 반복하는 또 다른 방법은 다음 find -print0과 같습니다.

while IFS= read -rd '' file <&3; do
  something "$file" 3<&-
done 3< <(find . -print0)

read -d ''개행으로 구분된 레코드 대신 NUL로 구분된 레코드를 읽습니다.

bash-4.4find -print0위의 내용은 반환된 파일을 배열에 저장할 수도 있습니다 .

readarray -td '' files < <(find . -print0)

동등함 zsh(종료 상태가 보존된다는 장점이 있음 find):

files=(${(0)"$(find . -print0)"})

를 사용하면 대부분의 표현식을 재귀 와일드카드와 와일드카드 한정자의 조합으로 변환 zsh할 수 있습니다 . find예를 들어 루프는 find . -name '*.txt' -type f -mtime -1다음과 같습니다.

for file (./**/*.txt(ND.m-1)) cmd $file

또는

for file (**/*.txt(ND.m-1)) cmd -- $file

--(동일한 need와 마찬가지로 **/*파일 경로가 로 시작하지 않으므로 ./eg로 시작될 수도 있으니 주의하세요.)-

ksh93그리고 bash결국 (재귀적 와일드카드의 고급 형태는 아니지만)에 대한 지원이 추가되었지만 **/여전히 와일드카드 한정자가 없었기 때문에 사용이 **매우 제한되었습니다. 또한 bash4.3 이전 버전에서는 디렉토리 트리를 내려갈 때 심볼릭 링크를 따랐습니다.

루프와 마찬가지로 $(find .)이는 전체 파일 목록을 메모리에 저장하는 것을 의미합니다. 어떤 경우에는 파일에 대한 작업이 파일에 영향을 미치게 하고 싶지 않을 때 이것이 바람직할 수도 있습니다.발견하다파일 수(예: 파일을 더 추가하면 결국 검색될 수 있음)

기타 신뢰성/보안 고려사항

경쟁 조건

이제 신뢰성에 대해 이야기한다면 파일을 언급 find/ zsh찾아 파일이 사용되는 시간 사이의 경쟁 조건과 표준을 준수하는지 확인해야 합니다(톡투게임).

디렉토리 트리를 내려갈 때에도 심볼릭 링크를 따르지 않고 TOCTOU 경합 없이 수행하는지 확인해야 합니다. find( find적어도 GNU) openat()올바른 O_NOFOLLOW플래그(지원되는 경우)를 사용하여 디렉터리를 열고 각 디렉터리에 대해 파일 설명자를 열어 두어 이를 수행합니다. // zsh이렇게 하지 마세요. 따라서 공격자가 적시에 디렉토리를 심볼릭 링크로 교체할 수 있으면 결국 잘못된 디렉토리로 내려갈 수 있습니다.bashksh

findwith가 디렉토리를 올바르게 내려오더라 도 , 예를 들어 or 와 같이 실행되면 훨씬 더 -exec cmd {} \;그렇습니다. 심볼릭 링크의 속성은 (그리고 충분한 파일이 호출될 때까지 기다릴 수 있도록 경합 창이 더 커집니다).-exec cmd {} +cmdcmd ./foo/barcmd ./foo/bar ./foo/bar/bazcmd./foo/barbarfind./foo-exec {} +findcmd

일부 구현에는 두 번째 문제를 완화하기 위한 find(비표준) 조건자가 있습니다 .-execdir

그리고:

find . -execdir cmd -- {} \;

find chdir()를 실행하기 전에 파일의 상위 디렉터리로 이동하세요 cmd. 호출이 아니라 호출 cmd -- ./foo/bar이므로 cmd -- ./bar( cmd -- bar일부 구현의 경우 호출임) --심볼릭 링크로 변경하는 문제를 피할 수 있습니다. ./foo이렇게 하면 rm유사한 명령(여전히 다른 파일을 삭제할 수 있지만 다른 디렉터리에 있는 파일은 삭제할 수 없음)을 사용하는 것이 더 안전하지만 심볼릭 링크를 따르지 않도록 설계되지 않은 한 파일을 수정할 수 있는 명령은 사용할 수 없습니다.

-execdir cmd -- {} +때로는 작동하지만 일부 버전의 GNU를 포함한 여러 구현 find에서는 -execdir cmd -- {} \;.

-execdir또한 너무 깊은 디렉토리 트리와 관련된 일부 문제를 해결하는 이점도 있습니다.

존재하다:

find . -exec cmd {} \;

지정된 경로의 크기는 cmd파일이 있는 디렉터리의 깊이에 따라 증가합니다. 크기가 더 큰 경우 PATH_MAX(Linux의 경우 약 4k) cmd해당 경로에서 수행된 모든 시스템 호출은 오류와 함께 실패합니다 ENAMETOOLONG.

의 경우 -execdir파일 이름(접두사 포함 가능 ./)만 에 전달됩니다 cmd. 파일 이름 자체는 대부분의 파일 시스템에서 NAME_MAX훨씬 낮은 제한( )을 가지 PATH_MAX므로 ENAMETOOLONG이 오류가 발생할 가능성은 거의 없습니다.

바이트 및 문자

게다가 보안을 고려 find하고 파일 이름을 더 일반적으로 다룰 때 사실이 간과되는 경우가 많습니다. 대부분의 Unix 계열 시스템에서 파일 이름은 바이트 시퀀스입니다(파일 경로 값에서 0이 아닌 모든 바이트, 대부분의 시스템에서). (ASCII 기반(현재는 드문 EBCDIC 기반을 무시함)) 0x2f는 경로 구분 기호입니다.

이러한 바이트를 텍스트로 처리할지 여부는 애플리케이션에 달려 있습니다. 일반적으로 그렇습니다. 그러나 일반적으로 바이트에서 문자로의 변환은 사용자의 로케일과 환경에 따라 수행됩니다.

이는 주어진 파일 이름이 로케일에 따라 다른 텍스트 표현을 가질 수 있음을 의미합니다. 예를 들어, 바이트 시퀀스는 문자 세트 ISO-8859-1이 있는 로케일의 파일 이름을 해석하는 애플리케이션과 문자 세트 IS0-8859-5가 있는 로케일의 파일 이름을 해석하는 애플리케이션 에 63 f4 74 e9 2e 74 78 74적합합니다 .côté.txtcєtщ.txt

더 나쁜. 문자 집합이 UTF-8(현재 표준)인 로케일에서는 63 f4 74 e9 2e 74 78 74를 문자에 전혀 매핑할 수 없습니다!

find-name파일 이름을 해당 / 술어 에 대한 텍스트로 처리하는 응용 프로그램입니다 -path(그리고 더 많은 유사 -iname하거나 -regex일부 구현이 있음).

find예를 들어 이는 여러 구현( findGNU 시스템의 GNU 포함) 이 있음을 의미합니다 .

find . -name '*.txt'

63 f4 74 e9 2e 74 78 74UTF-8 로케일에서 호출하면 위의 파일을 찾을 수 없습니다 *(0개 이상과 일치).수치, 바이트 아님)은 문자가 아닌 문자와 일치할 수 없습니다.

LC_ALL=C find...C 로케일은 문자당 1바이트를 의미하고 모든 바이트 값은 (일반적으로) 문자에 매핑되도록 보장되기 때문에 이 문제를 해결할 수 있습니다(일부 바이트 값의 경우 정의되지 않을 수 있음).

이제 셸에서 이러한 파일 이름을 반복할 때 바이트 대 문자도 문제가 될 수 있습니다. 이와 관련하여 우리는 일반적으로 4가지 주요 유형의 쉘을 볼 수 있습니다.

  1. 예를 들어 여전히 멀티바이트를 지원하지 않는 경우 dash에는 1바이트가 1문자에 매핑됩니다. 예를 들어 UTF-8에서는 côté4자이지만 6바이트입니다. UTF-8이 문자 세트인 로케일에서는

     find . -name '????' -exec dash -c '
       name=${1##*/}; echo "${#name}"' sh {} \;
    

    findUTF-8로 인코딩된 4자로 구성된 이름을 가진 파일은 성공적으로 검색되지만 dash길이 범위는 4~24로 보고됩니다.

  2. yash: 그 반대다. 그것은 단지 포함수치. 필요한 모든 입력은 내부적으로 문자로 변환됩니다. 이는 가장 일관된 셸을 제공하지만 임의의 바이트 시퀀스(유효한 문자로 변환할 수 없는 시퀀스)를 처리할 수 없음을 의미하기도 합니다. C 로케일에서도 0x7f 이상의 바이트 값을 처리할 수 없습니다.

     find . -exec yash -c 'echo "$1"' sh {} \;
    

    côté.txt예를 들어, UTF-8 로케일에서는 이전 ISO-8859-1이 실패합니다.

  3. 멀티바이트 지원이 선호되거나 bash점진적 으로 추가된 zsh경우 . 이는 문자에 매핑될 수 없는 바이트를 문자인 것처럼 고려하는 것으로 대체됩니다. 여전히 몇 가지 버그가 있습니다. 특히 GBK 또는 BIG5-HKSCS와 같이 덜 일반적인 멀티바이트 문자 세트의 경우(많은 멀티바이트 문자에 0-127 섹션 범위의 단어(예: ASCII 문자)가 포함되어 있기 때문에 상당히 짜증납니다).

  4. shFreeBSD(최소 11개) 와 같은 것 또는 mksh -o utf8-mode멀티바이트를 지원하지만 UTF-8만 지원하는 것입니다.

출력 인터럽트

중단되면 구문 분석 출력이 find또 다른 문제를 나타낼 수도 있습니다. 예를 들어 일부 제한을 유발하거나 어떤 이유로 종료되기 때문입니다.find -print0find

예:

$ (ulimit -t 1; find / -type f -print0 2> /dev/null) | xargs -r0 printf 'rm -rf "%s"\n' | tail -n 2
rm -rf "/usr/lib/x86_64-linux-gnu/guile/2.2/ccache/language/ecmascript/parse.go"
rm -rf "/usr/"
zsh: cpu limit exceeded (core dumped)  ( ulimit -t 1; find / -type f -print0 2> /dev/null; ) |
zsh: done                              xargs -r0 printf 'rm -rf "%s"\n' | tail -n 2

여기서는 findCPU 시간 제한에 도달하여 중단되었습니다. 출력이 버퍼링되었기 때문에(파이프로 들어갈 때) 여러 청크가 stdout으로 출력되었으며, 종료되었을 때 쓴 마지막 청크의 끝은 우연히 일부 파일 경로의 중간에 있었습니다 . 불행하게도 find여기에는 ./usr/lib/x86_64-linux-gnu/guile.../usr/

xargs, EOF가 뒤따르는 구분되지 않은 /usr/레코드를 보고 이를 에 전달했습니다 printf. 이 명령이 rm -rf반대였다면 심각한 결과가 발생할 수 있었습니다.


노트

1 완전성을 기하기 위해 zsh전체 목록을 메모리에 저장하지 않고 재귀 와일드카드를 사용하여 파일을 반복하는 영리한 방법을 언급할 수 있습니다.

process() {
  something with $REPLY
  false
}
: **/*(ND.m-1+process)

+cmdcmd현재 파일 경로로 호출되는(일반적으로 함수) glob 한정자입니다 $REPLY. 이 함수는 파일을 선택해야 하는지 여부를 결정하기 위해 true 또는 false를 반환합니다. 또한 $REPLY배열의 여러 파일을 수정하거나 반환 할 수도 있습니다 $reply. 여기서는 함수에서 처리하고 false를 반환하므로 파일이 선택되지 않습니다.

² GNU는 패턴 일치를 위해 find시스템의 fnmatch()libc 함수를 사용하므로 해당 함수가 텍스트가 아닌 데이터를 처리하는 방법에 따라 동작이 달라집니다.

답변3

이 답변은 매우 큰 결과 세트에 대한 것이며 주로 느린 네트워크를 통해 파일 목록을 가져올 때와 같은 성능과 관련이 있습니다. 소수의 파일(예: 로컬 디스크에 100개 또는 심지어 1000개)의 경우 대부분은 의미가 없습니다.

병렬성과 메모리 사용량

별거 문제 등과 관련된 다른 답변 외에도 또 다른 질문이 있습니다.

for file in `find . -type f -name ...`; do smth with ${file}; done

역따옴표 안의 부분은 줄 바꿈으로 분할하기 전에 먼저 완전히 평가되어야 합니다. 즉, 많은 수의 파일을 얻으면 개별 구성 요소에 존재하는 크기 제한으로 인해 차단될 수 있습니다. 제한이 없으면 어떤 경우에도 메모리가 부족해질 수 있습니다. 전체 목록이 출력 되고 첫 번째 목록이 실행되기 전에 find구문 분석됩니다 .forsmth

선호되는 UNIX 접근 방식은 본질적으로 병렬로 실행되고 일반적으로 임의로 큰 버퍼가 필요하지 않은 파이프를 사용하는 것입니다. 즉, 병렬로 find실행 smth하고 파일을 넘겨주는 동안 현재 파일 이름을 RAM에 유지하는 것이 좋습니다 smth.

적어도 부분적으로 실현 가능한 솔루션이 앞서 언급되었습니다 find -exec smth. 모든 파일 이름을 메모리에 보관할 필요가 없으며 병렬로 원활하게 실행됩니다. 불행히도 smth각 파일에 대한 프로세스도 시작됩니다. 하나의 파일만 처리할 수 있다면 smth그 파일이어야 합니다.

가장 좋은 해결책은 가능하다면 STDIN에서 파일 이름을 처리 find -print0 | smth할 수 있는 것 입니다. smth그러면 파일이 아무리 많아도 하나의 smth프로세스만 있고 두 프로세스 사이에 적은 수의 바이트만 버퍼링하면 됩니다(내부 파이프 버퍼링이 진행되는 것과 상관없이). 물론 이것이 smth표준 Unix/POSIX 명령이라면 상당히 비현실적일 것입니다. 그러나 직접 작성한다면 이것이 좋은 방법이 될 수 있습니다.

이것이 가능하지 않다면 이것이 find -print0 | xargs -0 smth더 나은 해결책 중 하나일 수 있습니다. 의견에서 @dave_thompson_085가 언급했듯이 시스템 제한(기본적으로 128KB 범위 또는 시스템에서 부과한 제한)에 도달하면 xargs매개변수가 실제로 여러 실행에 걸쳐 분할되며 실행 횟수를 선택할 수 있습니다. 영향을 받는 파일에 대해 단일 호출이 제공되므로 프로세스 수와 초기 대기 시간 간의 균형을 찾습니다.smthexecsmthsmth

편집: "최고"라는 개념을 제거했습니다. 더 나은 것이 나올지 말하기는 어렵습니다. ;)

답변4

찾기 결과를 반복하는 것은 나쁜 습관이 아닙니다. 나쁜 습관(이 경우와 모든 경우)은가설귀하의 입력은 대신 특정 형식으로 되어 있습니다.(테스트 및 확인됨) 이는 특정 형식입니다.

tldr/cbf:find | parallel stuff

관련 정보