"어차피 반복할 건데, 왜 사용하지 않나요 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쉘 글로브를 포함하는 파일 이름 목록을 반환할 때그런 다음파일 목록을 검색하려면 쉘 글로브를 사용하는 것이 좋습니다!

다음을 고려하세요:

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>가 물음표( '?') 문자로 작성되도록 합니다. 출력이 터미널 장치로 전달되는 경우 구현에서는 기본적으로 이 옵션을 제공할 수 있습니다.

-1-(숫자 1입니다.)출력이 한 줄에 하나의 항목이 되도록 강제합니다.

Globbing에는 자체 문제가 없습니다 ?.어느?문자를 사용하므로 목록의 여러 일치 결과는 동일한 파일과 여러 번 일치합니다. 그건 쉽게 처리됩니다.

이 작업을 수행하는 방법이 핵심은 아니지만 결국 수행하는 데 많은 시간이 걸리지 않으며 아래에 설명되어 있습니다.왜 안 돼. 제가 생각하기에 그 질문에 대한 최선의 답변이 채택되었습니다. 나는 당신이 사람들에게 자신이 무엇을 하고 있는지 말하는 데 더 자주 집중하도록 제안하고 싶습니다.~할 수 있다그들이 하는 일보다 하는 일캔트.내 생각에 당신은 적어도 틀린 것으로 입증될 가능성이 훨씬 적습니다.

그런데 왜 시도합니까? 물론, 나의 주된 동기는 다른 사람들이 나에게 계속해서 내가 할 수 없다고 말하는 것이었습니다. ls나는 무엇을 찾아야 할지 아는 한 원하는 대로 출력이 규칙적이고 예측 가능하다는 것을 잘 알고 있습니다 . 잘못된 정보는 대부분의 일보다 나를 더 괴롭힌다.

그러나 진실은 Patrick과 Wumpus Q. Wumbley의 답변을 제외하고는 그렇습니다.(후자의 멋진 핸들에도 불구하고), 나는 여기 답변에 있는 대부분의 정보가 대부분 정확하다고 생각합니다. 쉘 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, :colon또는 영숫자 문자가 아닌 모든 문자를 보호한 다음 sort -u고유한 결과에 대한 목록을 보호하겠습니다. ls인쇄할 수 없는 문자를 이미 안전하게 보관했기 때문에 안전합니다 . 보다:

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 문자는 경로 이름에서 금지된 유일한 바이트라는 점을 기억하십시오. 나는 여기에 글로브를 따로 두고 대신에 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

마지막으로 훨씬 간단한 방법이 있습니다.파싱lsinode 번호가 필요할 때 자주 사용하는 방법입니다.

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

이는 단지 inode 번호를 반환합니다. 이는 또 다른 편리한 POSIX 지정 옵션입니다.

답변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 셸의 확장 버전은 요즘 쉽게 구할 수 있으며 bashzsh다 원본보다 훨씬 뛰어납니다. 핵심 "쉘 유틸리티"(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인쇄할 수 없는 문자를 glob 문자로 바꿉니다. yes, 하지만 해당 문자는 실제 파일 이름에 없습니다. 이것이 왜 중요합니까? 2가지 이유:

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

예를 들어:

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

정확히 동일하게 보이는 2개의 파일이 어떻게 있는지 확인하세요. 둘 다 로 표시되면 어떻게 구별할 수 있나요 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

쉘 글로브를 사용하면 완벽하게 작동합니다.

$ 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전혀 글로브가 아닙니다. ?"여기에 직접 표시할 수 없는 문자가 있습니다"라는 의미로 사용됩니다 . Globs는 ?"여기에는 모든 문자가 허용됩니다"를 의미하는 데 사용됩니다.

Globs에는 다른 특수 문자가 있습니다( *적어도 쌍 []안에는 []더 많은 문자가 있습니다). 그 중 어느 것도 에 의해 탈출되지 않습니다 ls -q.

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

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

ls -q셸에 다시 피드백할 수 있는 것을 생성하는 것이 아니라 미친 캐릭터로부터 눈 및/또는 터미널을 보호하기 위한 것입니다.

답변4

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

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

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

관련 정보