「いずれにせよ反復処理を実行するつもりなら、 を使用しないのはなぜですかls?」

「いずれにせよ反復処理を実行するつもりなら、 を使用しないのはなぜですかls?」

私はいつも、このリンク断言する「解析しないでくださいls!」これにはいくつかの理由があり、私は困惑しています。

  1. そのリンク先の情報はほとんど疑問視されることなくそのまま受け入れられているようですが、ざっと読んでみると少なくともいくつかの誤りに気付くことがあります。

  2. また、そのリンクに記載されている問題は、解決策を見つけたいという欲求を喚起していないようにも思われます。

最初の段落から:

...[ls]ファイルのリストを要求すると、大きな問題が発生します。Unix では、空白、改行、カンマ、パイプ記号、および NUL 以外の区切り文字として使用しようとするほぼすべての文字をファイル名に使用できます。... は、lsファイル名を改行で区切ります。ファイル名に改行が含まれるファイルがあるまでは、これで問題ありません。また、改行ではなく NUL 文字でファイル名を終了できる の実装を私は知らないためls、 でファイル名のリストを安全に取得することはできませんls

残念ですよね?これまで改行を含む可能性のあるデータに対して、改行で終了するリストされたデータセットを処理できますか? まあ、この Web サイトで質問に答える人がこの種のことを日常的に行わなかったら、私たちは何か問題を抱えていると思うかもしれません。

しかし、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シェルグロブを含むファイル名のリストを返すときその後ファイル リストを取得するには、シェル 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 の各インスタンスを疑問符 ( '?') 文字として書き込むように強制します。出力が端末デバイスに行われる場合、実装ではこのオプションがデフォルトで提供されることがあります。

-1-(数字の1です。)出力を 1 行につき 1 つのエントリに強制します。

グロッビングには問題がないわけではない。?マッチどれでも文字なので、?リスト内の複数の一致結果は同じファイルに複数回一致します。これは簡単に処理できます。

これをどうやって行うかは重要ではありませんが、結局のところ、それほど手間がかからず、以下で説明されています。私が興味を持ったのはなぜだめですか. 私が考えるに、その質問に対する最良の答えはすでに受け入れられています。人々に彼らが何を考えているのかを伝えることにもっと重点を置くことをお勧めします。できる彼らが何をするかよりもできません。私の考えでは、少なくともあなたが間違っていることが証明される可能性はずっと低いでしょう。

しかし、なぜ挑戦するのでしょうか? 正直なところ、私の主な動機は、他の人が私にできないと言い続けたことでした。ls何を探すべきかを知っていれば、出力は望むほど規則的で予測可能であることはよくわかっています。誤った情報は、ほとんどのことよりも私を悩ませます。

しかし、真実は、パトリックとウンパスQ.ワンブリーの回答の注目すべき例外を除いて、(後者のハンドルは素晴らしいのに)、私はここでの回答のほとんどの情報がほぼ正しいと考えています。シェル グロブは、現在のディレクトリを検索する場合、解析よりも使いやすく、一般的に効果的ですls。ただし、少なくとも私にとっては、上記の記事で引用されている誤った情報を広めることを正当化するのに十分な理由ではありませんし、「解析しないでくださいls

zshパトリックの回答の矛盾した結果は、主にthenを使用した結果であることに注意してくださいbashzshデフォルトでは、単語分割$(コマンドは置換)結果を移植可能な方法で実行しません。そのため、彼が質問すると、残りのファイルはどこに行ったのでしょうか?その質問の答えはあなたの殻がそれらを食べました。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または英数字以外のすべての文字を safe にして、sort -uリスト内の一意の結果を得ます。これは、印刷できない文字がすでに safe されているので安全です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'

以下では、別の方法論を使って、この問題に再度取り組みます。null 以外では\0/ASCII 文字はパス名で禁止されている唯一のバイトであることを覚えておいてください。ここでは glob を脇に置いて、代わりに-dの POSIX 指定オプションlsと の POSIX 指定-exec $cmd {} +構造を組み合わせます。は自然に 1 つだけを順番に出力するfindため、以下は、すべてのエントリのすべての dentry 情報を含む、再帰的で確実に区切られたファイルリストを簡単に取得します。次のようなもので何ができるか想像してみてください。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]*'

これは単に inode 番号を返します。これはもう 1 つの便利な 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 シェルの拡張バージョンは今日では簡単に入手できます。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印刷できない文字を glob 文字に置き換えますが、それらの文字は実際のファイル名には存在しません。なぜこれが問題になるのでしょうか? 理由は 2 つあります。

  1. そのファイル名をプログラムに渡した場合、そのファイル名は実際には存在しません。実際のファイル名を取得するには、glob を展開する必要があります。
  2. ファイル glob は複数のファイルに一致する可能性があります。

例えば:

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

まったく同じように見える 2 つのファイルがあることに注目してください。両方が として表現されている場合、どのようにして区別するのでしょうかa?b


著者は、ls がシェル グロブを含むファイル名のリストを返すことをファイル名の文字化けと呼び、シェル グロブを使用してファイル リストを取得することを推奨しています。

ここでは違いがあります。示されているように、グロブが返されると、そのグロブは複数のファイルに一致する可能性があります。ただし、グロブに一致する結果を反復処理すると、グロブではなく、正確なファイルが返されます。

例えば:

$ 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

では、実際の glob を使ってみましょう。

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

bashで

上記の例は、通常のシェルである 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

シェル glob を使用すると、問題なく動作します。

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

bash がこのように動作する理由は、回答の冒頭で述べた点の 1 つ、「ファイル 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グロブではありません。これは?「ここには直接表示できない文字があります」という意味です。グロブは?「ここには任意の文字が許可されます」という意味です。

グロブには他の特殊文字 (少なくとも と のペアの中にはさらにあります) があり*ます[][]これらのいずれも によってエスケープされませんls -q

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

ls -1q出力をグロブのセットとして扱い、それを展開すると、 x2 回取得されるだけでなく、完全に失われます[x]。グロブとしては、文字列としてそれ自体と一致しません。

ls -qは、奇妙な文字から目や端末を保護するためのものであり、シェルにフィードバックできるものを生成するためのものではありません。

答え4

答えは簡単です。処理しなければならない特殊なケースが、考えられるメリットを上回ります。出力lsを解析しなければ、これらの特殊なケースを回避できます。ls

ここでのマントラはユーザーのファイルシステムを決して信用しない(同等のユーザーの入力を決して信用しない)。100%の確実性で常に機能する方法があるなら、ls同じ効果があっても確実性が低い方法であっても、その方法を選択するべきです。技術的な詳細については、テルドンそしてパトリック広範囲に。自分の仕事や名誉がかかっている重要な(そしておそらく高額な)取引で使用するリスクがあるためls、回避できるのであれば、不確実性のないソリューションを好むだろうとわかっています。

好む人もいるのは知っています確実性よりもリスクを優先、 しかしバグレポートを提出しました

関連情報