"어쨌든 반복할 건데, 왜 사용하지 않나요 ls?"

"어쨌든 반복할 건데, 왜 사용하지 않나요 ls?"

인용된 답변이 계속 표시됩니다.이 링크명확하게 진술하다"파싱하지 마세요 ls!"이것이 나를 괴롭히는 이유는 다음과 같습니다.

  1. 해당 링크에 있는 정보는 아무 문제 없이 대량으로 받아들여지는 것 같지만, 가볍게 읽으면서 최소한 몇 가지 오류는 찾아낼 수 있습니다.

  2. 해당 링크에 언급된 문제 역시 해결책을 찾고자 하는 욕구를 불러일으키지 않는 것 같습니다.

첫 번째 단락부터:

... [ls]파일 목록을 요청할 때 큰 문제가 있습니다. Unix는 공백, 개행, 쉼표, 파이프 기호 및 파일 이름으로 사용하려는 거의 모든 문자를 포함하여 파일 이름에 거의 모든 문자를 허용합니다. NUL 이외의 구분 기호 문자입니다. ... ls줄 바꿈으로 파일 이름을 구분합니다. 파일 이름에 개행 문자가 포함될 때까지는 괜찮습니다. ls줄 바꿈 문자 대신 NUL 문자로 파일 이름을 종료할 수 있는 구현을 알지 못하기 때문에 ls.

정말 짜증나는 일이죠? 어떻게한 번줄 바꿈이 포함될 수 있는 데이터에 대해 줄 바꿈으로 끝나는 나열된 데이터 세트를 처리할 수 있습니까? 글쎄, 이 사이트의 질문에 답변하는 사람들이 매일 이런 일을 하지 않았다면 아마도 우리가 문제에 빠졌다고 생각할 것입니다.

그러나 사실 대부분의 ls구현은 출력을 구문 분석하기 위한 매우 간단한 API를 제공하며 우리는 이를 깨닫지도 못한 채 항상 이를 수행합니다. 파일 이름을 null로 끝낼 수 있을 뿐만 아니라 null이나 원하는 임의의 문자열로 시작할 수도 있습니다. 게다가 이러한 임의의 문자열을 할당할 수도 있습니다.파일 유형별. 다음을 고려하십시오:

LS_COLORS='lc=\0:rc=:ec=\0\0\0:fi=:di=:' ls -l --color=always | cat -A
total 4$
drwxr-xr-x 1 mikeserv mikeserv 0 Jul 10 01:05 ^@^@^@^@dir^@^@^@/$
-rw-r--r-- 1 mikeserv mikeserv 4 Jul 10 02:18 ^@file1^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 01:08 ^@file2^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 02:27 ^@new$
line$
file^@^@^@$
^@

바라보다이것더 알아보기.

이제 제가 정말 관심을 갖는 부분은 이 기사의 다음 부분입니다.

$ ls -l
total 8
-rw-r-----  1 lhunath  lhunath  19 Mar 27 10:47 a
-rw-r-----  1 lhunath  lhunath   0 Mar 27 10:47 a?newline
-rw-r-----  1 lhunath  lhunath   0 Mar 27 10:47 a space

문제는 의 출력에서 ls​​사용자나 컴퓨터 중 어느 부분이 파일 이름을 구성하는지 알 수 없다는 것입니다. 그것은 모든 단어입니까? 아니요, 한 줄씩인가요? 아니요. 이 질문에는 정답이 없습니다. 단, 당신이 모른다는 점을 제외하면 말이죠.

또한 ls때로는 파일 이름 데이터가 깨질 수도 있습니다(이 경우에는 \n단어 사이에 문자가 들어갑니다)."ㅏ"그리고 "새로운 팀"가 되다? 물음표...

...

현재 디렉터리의 모든 파일을 반복하려면 for루프와 glob을 사용하세요.

for f in *; do
    [[ -e $f ]] || continue
    ...
done

작가는 그렇게 부른다잘못된 파일 이름ls쉘 전역 변수를 포함하는 파일 이름 목록을 반환하는 경우그런 다음파일 목록을 검색하려면 Shell glob을 사용하는 것이 좋습니다!

다음을 고려하세요:

printf 'touch ./"%b"\n' "file\nname" "f i l e n a m e" |
    . /dev/stdin
ls -1q

f i l e n a m e  
file?name

IFS="
" ; printf "'%s'\n" $(ls -1q)

'f i l e n a m e'
'file
name'

POSIX 정의그리고 피연산자는 다음과 -1같습니다 -q ls.

-q- 인쇄할 수 없는 파일 이름 문자와 <tab>s의 모든 인스턴스를 물음표( '?') 문자로 강제로 작성합니다. 구현은 출력이 터미널 장치로 이루어지는 경우 기본적으로 이 옵션을 제공할 수 있습니다(MAY).

-1-(첫번째.)한 줄에 하나의 항목으로 출력되도록 합니다.

와일드카드에는 고유한 문제가 있습니다 ?.어느문자이므로 ?목록의 여러 일치 항목은 동일한 파일과 여러 번 일치합니다. 이것은 다루기가 쉽습니다.

이를 수행하는 방법이 핵심은 아니지만 결국 수행할 작업이 많지 않으며 아래에 설명되어 있습니다. 제가 관심을 갖는 것은 무엇입니까?왜 안 돼. 제 생각에는 이 질문에 대한 가장 좋은 대답은 받아들여지는 것입니다. 사람들에게 그들이 알고 있는 것을 알리는 데 더 집중하는 것이 좋습니다.할 수 있는그들이 하는 일보다 하는 일할 수 없습니다.적어도 당신이 틀렸다는 것이 입증될 가능성은 훨씬 적다고 생각합니다.

그런데 왜 시도합니까? 물론, 나의 주된 동기는 다른 사람들이 나에게 할 수 없다고 계속해서 말한 것이었습니다. ls무엇을 찾아야 할지 아는 한 결과는 원하는 대로 규칙적이고 예측 가능하다는 것이 매우 분명합니다 . 오류 메시지는 대부분의 것보다 나를 더 짜증나게 합니다.

그러나 문제는 Patrick과 Wumpus Q를 제외하고는 그렇습니다. 웜블리의 답변(후자가 훌륭한 핸들을 가지고 있지만)나는 여기에 있는 답변에 있는 대부분의 정보가 대부분 정확하다고 생각합니다. 쉘 glob은 현재 디렉토리를 검색할 때 구문 분석하는 것보다 사용하기 쉽고 일반적으로 더 효율적입니다 ls. 그러나 적어도 내 생각에는 위 기사에 인용된 잘못된 정보를 퍼뜨리는 것을 정당화하기에는 충분하지 않으며 "받아들일 수 있는 이유"도 아닙니다.구문 분석되지 않았습니다 ls."

zshPatrick의 답변에서 일관되지 않은 결과는 주로 then 을 사용한 결과입니다 bash. - 기본적으로 - 단어 분할 명령의 결과는 zsh이식 가능한 방식으로 대체되지 않습니다 . 그래서 그가 물었을 때$()나머지 파일들은 어디로 갔나요?이 질문에 대한 대답은당신의 껍질이 그들을 먹습니다.이것이 바로 이식 가능한 쉘 코드로 작업할 때 이 변수를 설정해야 하는 이유입니다 SH_WORD_SPLIT. zsh나는 그의 답변에서 이것을 언급하지 않은 것이 매우 오해의 소지가 있다고 생각합니다.

Wumpus의 답변이 나에게 적합하지 않았습니다. 목록 컨텍스트의 ?역할쉘볼. 무슨 말을 더 해야 할지 모르겠습니다.

결과가 여러 개인 경우를 처리하려면 glob의 욕심을 제한해야 합니다. 다음은 끔찍한 파일 이름의 테스트 라이브러리를 생성하여 표시합니다.

{ printf %b $(printf \\%04o `seq 0 127`) |
sed "/[^[-b]*/s///g
        s/\(.\)\(.\)/touch '?\v\2' '\1\t\2' '\1\n\2'\n/g" |
. /dev/stdin

echo '`ls` ?QUOTED `-m` COMMA,SEP'
ls -qm
echo ; echo 'NOW LITERAL - COMMA,SEP'
ls -m | cat
( set -- * ; printf "\nFILE COUNT: %s\n" $# )
}

산출

`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b

NOW LITERAL - COMMA,SEP
?
 \, ?
     ^, ?
         `, ?
             b, [       \, [
\, ]    ^, ]
^, _    `, _
`, a    b, a
b

FILE COUNT: 12

/slash이제 , 또는 영숫자가 아닌 쉘 글로브의 모든 문자를 보호 -dash한 다음 고유한 결과 목록을 보호하겠습니다. 인쇄할 수 없는 모든 문자가 저장되었기 때문에 안전합니다. 보다::colonsort -uls

for f in $(
        ls -1q |
        sed 's|[^-:/[:alnum:]]|[!-\\:[:alnum:]]|g' |
        sort -u | {
                echo 'PRE-GLOB:' >&2
                tee /dev/fd/2
                printf '\nPOST-GLOB:\n' >&2
        }
) ; do
        printf "FILE #$((i=i+1)): '%s'\n" "$f"
done

산출:

PRE-GLOB:
[!-\:[:alnum:]][!-\:[:alnum:]][!-\:[:alnum:]]
[!-\:[:alnum:]][!-\:[:alnum:]]b
a[!-\:[:alnum:]]b

POST-GLOB:
FILE #1: '?
           \'
FILE #2: '?
           ^'
FILE #3: '?
           `'
FILE #4: '[     \'
FILE #5: '[
\'
FILE #6: ']     ^'
FILE #7: ']
^'
FILE #8: '_     `'
FILE #9: '_
`'
FILE #10: '?
            b'
FILE #11: 'a    b'
FILE #12: 'a
b'

아래에서는 이 문제에 다시 접근하지만 다른 접근 방식을 사용합니다. \0널을 제외 하고 /ASCII 문자는 경로 이름에서 금지되는 유일한 바이트라는 점을 기억하십시오 . 저는 glob을 따로 남겨두고 대신 POSIX 지정 -d옵션 ls과 POSIX 지정 -exec $cmd {} +구문을 결합했습니다 find. find자연적으로 하나만 순서대로 내보내지기 때문에 /다음을 사용하면 각 항목에 대한 모든 디렉터리 항목 정보를 포함하여 반복적이고 안정적으로 구분된 파일 목록을 쉽게 얻을 수 있습니다. 다음과 같이 무엇을 할 수 있을지 상상해 보세요.

#v#note: to do this fully portably substitute an actual newline \#v#
#v#for 'n' for the first sed invocation#v#
cd ..
find ././ -exec ls -1ldin {} + |
sed -e '\| *\./\./|{s||\n.///|;i///' -e \} |
sed 'N;s|\(\n\)///|///\1|;$s|$|///|;P;D'

###OUTPUT

152398 drwxr-xr-x 1 1000 1000        72 Jun 24 14:49
.///testls///

152399 -rw-r--r-- 1 1000 1000         0 Jun 24 14:49
.///testls/?
            \///

152402 -rw-r--r-- 1 1000 1000         0 Jun 24 14:49
.///testls/?
            ^///

152405 -rw-r--r-- 1 1000 1000         0 Jun 24 14:49
.///testls/?
        `///
...

ls -i매우 유용할 수 있습니다. 특히 결과의 고유성이 의심되는 경우에 그렇습니다.

ls -1iq | 
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' | 
tr -d '\n' | 
xargs find

이것들은 제가 생각할 수 있는 가장 휴대성이 뛰어난 수단입니다. GNU를 사용하면 ls다음과 같은 작업을 수행할 수 있습니다.

ls --quoting-style=WORD

마지막으로 더 간단한 방법이 있습니다.분석하다ls나는 inode 번호가 필요할 때 이것을 자주 사용합니다:

ls -1iq | grep -o '^ *[0-9]*'

이는 또 다른 편리한 POSIX 관련 옵션인 inode 번호만 반환합니다.

답변1

나는 이것을 전혀 믿지 않습니다. 그러나 논쟁을 위해 당신이 가정합시다할 수 있다, 충분한 노력을 기울일 준비가 되어 있다면 ls"적대자"(당신이 작성한 코드를 알고 의도적으로 코드를 손상시키도록 설계된 파일 이름을 선택한 사람)에 대해서도 출력을 안정적으로 구문 분석할 수 있습니다.

이렇게 할 수는 있어도,이건 아직도 ​​안 좋은 생각이야.

Bourne Shell 1은 끔찍한 언어입니다. 극단적인 이식성이 다른 요소(예: )보다 더 중요하지 않은 한 복잡한 것에 사용해서는 안 됩니다 autoconf.

ls구문 분석된 출력이 쉘 스크립트에 대한 저항이 가장 적은 경로인 것처럼 보이는 문제가 있는 경우 이는 현재 수행 중인 작업이 다음과 같다는 강력한 표시입니다.쉘 스크립트가 너무 복잡함Perl, Python, Julia 또는 다른 언어로 전체 내용을 다시 작성해야 합니다.좋아요사용하기 쉬운 스크립팅 언어. 데모로서, 이것은 Python으로 작성한 마지막 프로그램입니다:

import os, sys
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
      ino = os.lstat(os.path.join(subdir, f)).st_ino
      sys.stdout.write("%d %s %s\n" % (ino, subdir, f))

파일 이름에 특이한 문자가 있어도 문제가 없습니다.산출은 출력이 ls모호하지 않은 것처럼 모호하지 않습니다. 그러나 이것은 결과가 직접 사용되는 "실제" 프로그램(이런 데모와는 반대로)에서는 중요하지 않습니다 os.path.join(subdir, f).

또한 작성한 내용과 달리 지금부터 6개월이 지나도 여전히 의미가 있고 약간 다른 작업을 수행해야 할 때 쉽게 수정할 수 있다는 점도 중요합니다. 예를 들어, 도트 파일과 편집기 백업을 제외하고 모든 것을 기본 이름을 기준으로 알파벳순으로 처리해야 한다고 가정해 보겠습니다.

import os, sys
filelist = []
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
        if f[0] == '.' or f[-1] == '~': continue
        lstat = os.lstat(os.path.join(subdir, f))
        filelist.append((f, subdir, lstat.st_ino))

filelist.sort(key = lambda x: x[0])
for f, subdir, ino in filelist: 
   sys.stdout.write("%d %s %s\n" % (ino, subdir, f))

1 예, Bourne 셸의 확장 버전은 이제 쉽게 사용할 수 있으며 bash모두 zsh원래 버전보다 훨씬 좋습니다. 핵심 "쉘 유틸리티"(find, grep 등)에 대한 GNU 확장도 많은 도움이 됩니다. 하지만 모든 확장에도 불구하고 쉘 환경은 개선되지 않습니다충분한실제로 좋은 스크립팅 언어와 경쟁하려면 어떤 쉘에 대해 이야기하든 "복잡한 작업을 수행하는 데 쉘을 사용하지 마십시오"라는 조언이 남아 있습니다.

"좋은 스크립팅 언어이기도 한 좋은 대화형 쉘은 어떤 모습일까요?"는 대화형 CLI에 필요한 편의성(예: 대신 입력 허용 cc -c -g -O2 -o foo.o foo.c) subprocess.run(["cc", "-c", "-g", "-O2", "-o", "foo.o", "foo.c"])과 복잡한 스크립트 오류의 미묘함(예:아니요임의의 위치에 있는 인용되지 않은 단어를 문자열 리터럴로 해석합니다. 이런 것을 디자인하려고 한다면 아마도 IPython, PowerShell, Lua를 먼저 블렌더에 던질 것입니다. 그러나 결과가 어떻게 나올지는 모르겠습니다.

답변2

이 링크는 정보가 완전히 정확하고 오랫동안 사용되었기 때문에 여러 번 참조되었습니다.


ls인쇄할 수 없는 문자를 전역 문자로 바꾸기 예, 하지만 해당 문자는 실제 파일 이름에 없습니다. 이것이 왜 중요합니까? 2가지 이유:

  1. 해당 파일 이름을 프로그램에 전달하면 파일 이름은 실제로 존재하지 않습니다. 실제 파일 이름을 얻으려면 글로브를 확장해야 합니다.
  2. 파일 glob은 여러 파일과 일치할 수 있습니다.

예를 들어:

$ touch a$'\t'b
$ touch a$'\n'b
$ ls -1
a?b
a?b

완전히 똑같아 보이는 두 개의 파일이 있다는 점에 유의하세요. 둘 다 로 표시된다면 어떻게 구별할 수 있나요 a?b?


ls가 쉘 글로브를 포함하는 파일 이름 목록을 반환할 때 작성자는 이를 잘못된 파일 이름으로 참조한 다음 쉘 글로브를 사용하여 파일 목록을 검색할 것을 권장합니다!

여기에는 차이가 있습니다. 그림에 표시된 것처럼 glob을 반환하면 glob이 여러 파일과 일치할 수 있습니다. 그러나 glob과 일치하는 결과를 반복하면 glob이 아닌 정확한 파일이 반환됩니다.

예를 들어:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

출력에 가 아닌 원래 문자가 포함되어 있는 것으로 xxd나타나는지 확인하십시오 .$file\t\n?

을 사용하면 ls다음과 같은 결과를 얻을 수 있습니다.

for file in $(ls -1q); do printf '%s' "$file" | xxd; done
0000000: 613f 62                                  a?b
0000000: 613f 62                                  a?b

"어쨌든 반복할 건데, 왜 사용하지 않나요 ls?"

당신이 준 예는 실제로 작동하지 않습니다. 작동하는 것처럼 보이지만 그렇지 않습니다.

나는 이것을 언급하고 있습니다 :

 for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done

나는 여러 파일 이름으로 디렉터리를 만들었습니다.

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

코드를 실행하면 다음과 같은 결과가 나타납니다.

$ for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done
./a b
./a b

나머지 파일들은 어디로 갔나요?

이것을 시도해 봅시다:

$ for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a??b’: No such file or directory
./a b
./a b
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a?b’: No such file or directory

이제 실제 글로브를 사용해 보겠습니다.

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./a b
./a b
./a b
./a
b

배쉬와 함께

위의 예에서는 일반 쉘 zsh를 사용하고 있습니다. Bash를 사용하여 프로세스를 반복하면 예제와 완전히 다른 또 다른 결과 집합이 나타납니다.

동일한 파일 세트:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

코드와 완전히 다른 결과:

for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
./a b
./a b
./a b
./a b
./a
b
./a  b
./a b
./a b
./a b
./a b
./a b
./a b
./a
b
./a b
./a b
./a b
./a b
./a
b

Shell glob을 사용하면 매우 잘 작동합니다.

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./a b
./a b
./a b
./a
b

bash가 이런 방식으로 동작하는 이유는 제가 답변 시작 부분에서 언급한 "파일 glob이 여러 파일과 일치할 수 있습니다"라는 점 중 하나로 거슬러 올라갑니다.

ls여러 파일에 대해 동일한 glob( a?b)을 반환하므로 이 glob이 확장될 때마다 일치하는 모든 파일을 얻습니다.


사용 중인 파일 목록을 다시 생성하려면 어떻게 해야 합니까?

touch 'a b' 'a  b' a$'\xe2\x80\x82'b a$'\xe2\x80\x83'b a$'\t'b a$'\n'b

16진수 코드는 UTF-8 NBSP 문자입니다.

답변3

의 출력은 ls -q전혀 구형이 아닙니다. ?"여기에 직접 표시할 수 없는 문자가 있습니다"라는 뜻입니다 . Glob은 ?"여기에 허용되는 모든 문자"를 의미했습니다.

Globs 다른 특수 문자가 있습니다( *적어도 이 쌍에는 더 많은 특수 []문자 []가 있습니다). 이들 중 어느 것도 탈출하지 못했습니다 ls -q.

$ touch x '[x]'
$ ls -1q
[x]
x

출력을 글로브 세트로 처리하고 확장 하면 두 번 ls -1q얻을 뿐만 아니라 완전히 놓칠 수도 있습니다. glob으로서는 문자열 자체와 일치하지 않습니다.x[x]

ls -q이는 이상한 캐릭터로부터 눈 및/또는 단말기를 보호하기 위한 것이지 쉘에 다시 피드백할 수 있는 것을 생성하기 위한 것이 아닙니다.

답변4

대답은 간단합니다. ls처리해야 하는 특수한 상황이 가능한 이점보다 더 큽니다. ls출력을 구문 분석하지 않으면 이러한 특별한 경우를 피할 수 있습니다.

여기에 있는 만트라는 다음과 같습니다.사용자 파일 시스템을 절대 신뢰하지 마십시오(동등사용자 입력을 절대 신뢰하지 마세요). 항상 100% 확실하게 작동하는 방법이 있다면 비록 ls동일하게 작동하지만 확실성은 떨어지더라도 선호하는 방법이어야 합니다. 기술적 세부 사항은 이미 다루었으므로 다루지 않겠습니다.테든그리고패트릭넓게. 나는 중요한(아마도 비용이 많이 드는) 거래에서 내 직업/평판을 사용할 위험이 있기 때문에 ls피할 수 있다면 불확실성이 없는 솔루션을 선호한다는 것을 알고 있습니다.

어떤 사람들은 더 좋아한다는 걸 알아요위험이 확실성보다 더 중요합니다, 하지만버그 보고서를 제출했습니다..

관련 정보