なぜlsを*解析*しないのですか(そして代わりに何をすべきか)?


204

私は一貫して引用の回答を参照このリンクを明確に述べ、「解析はいけませんのls!」これにはいくつかの理由があります。

  1. そのリンクの情報は、ささいな質問なしに大々的に受け入れられているように見えますが、偶然の読書で少なくともいくつかのエラーを見つけることができます。

  2. また、あたかもそのリンクに記載されている問題が解決策を見つけたいという欲求を引き起こしていないかのようです。

最初の段落から:

... [ls]ファイルのリストを要求すると、大きな問題があります。Unixでは、空白、改行、コンマ、パイプ記号など、ほとんどすべての文字をファイル名に使用できます。 NULを除く区切り文字。... lsファイル名を改行で区切ります。これは、名前に改行を含むファイルが作成されるまで問題ありません。そして、ls改行の代わりにNUL文字でファイル名を終了できるようにする実装がわからないため、これを使用してファイル名のリストを安全に取得できなくなりますls

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

ただし、ls実際には、ほとんどの実装では、出力を解析するための非常に単純なAPIが実際に提供されており、私たちはみな、気づかずにそれをずっと行ってきました。ファイル名をnullで終了できるだけでなく、nullで開始することも、他の任意の文字列で開始することもできます。さらに、これらの任意の文字列をfile-typeごとに割り当てることができます。考えてください:

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ループとグロブを使用します。

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>sの各インスタンスを強制的に疑問符('?')文字として書き込みます。出力が端末デバイスに対するものである場合、実装はデフォルトでこのオプションを提供します。

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

-グロブは、独自の問題がないわけではない?試合どんなので、複数の一致文字?リストの結果が同じファイルを複数回マッチします。それは簡単に処理できます。

このことをどのように行うかはポイントではありません-結局のところ、それほど多くのことをする必要はなく、以下に示されています-私はなぜそうでないことに興味がありました。私が考えているように、その質問に対する最良の答えは受け入れられました。できないことよりも、できることを伝えることに重点を置くことをお勧めします私が思うに、あなたは少なくとも間違っていると証明される可能性ははるかに低いです。

しかし、なぜ試してみるのでしょうか?確かに、私の主な動機は、他の人が私にできないと私に言い続けたことでした。私はそれを非常によく知っているlsあなたはあまりにも長い間、あなたが何を探すべきか知っているとして、それを望むことができるように、出力が定期的かつ予測可能なようです。誤報は、ほとんどのことよりも私を悩ませます。

しかし真実は、パトリックとWumpus Q. Wumbleyの両方の顕著な例外を除いて(後者の素晴らしいハンドルにもかかわらず)、ここでの答えのほとんどの情報はほとんど正しいと思います-シェルグロブはどちらもより使いやすいですそして一般に、構文解析よりも現在のディレクトリの検索に関してより効果的lsです。彼らは、少なくとも私に関しては、しかし、上記の記事で引用された誤った情報を伝播するいずれか正当化するのに十分な理由ではないも彼らがする許容正当化されている「パースことはありませんls

パトリックの答えの一貫性のない結果は、ほとんどが彼がzshthen を使用した結果であることに注意してくださいbashzsh-デフォルト- $(置換されたコマンドをワード分割しない)で、移植可能な方法で結果を返します。それで、彼が残りのファイルどこに行ったのかと尋ねるとその質問への答えは、あなたのシェルがそれらを食べたということです。これが、移植性のあるシェルコードをSH_WORD_SPLIT使用zshして処理するときに変数を設定する必要がある理由です。私は彼の答えでこれを指摘しなかったことが、ひどく誤解を招くと考えています。

Wumpusの答えは私にとっては計算されません-リストコンテキストでは、?文字シェルグロブです。他にそれを言う方法がわかりません。

複数の結果のケースを処理するには、グロブの貪欲さを制限する必要があります。以下は、ひどいファイル名のテストベースを作成して表示するだけです:

{ 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'

以下で問題に再度アプローチしますが、別の方法論を使用します。\0null 以外に、/ASCII文字がパス名で禁止されている唯一のバイトであることに注意してください。ここにglobを置いておき、代わりにPOSIXで指定された-dオプションとのためにlsPOSIXで指定された-exec $cmd {} +コンストラクトを組み合わせますfind。順番にfind1つしか自然に出力されないため/、以下は、すべてのエントリのすべてのdentry情報を含む再帰的で確実に区切られたファイルリストを簡単に取得します。次のようなことで何ができるか想像してみてください。

#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

最後に、iノード番号が必要なときにたまたま頻繁に使用する、はるかに簡単な解析ls方法を示します。

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

これはinode番号を返すだけです-これは別の便利なPOSIX指定オプションです。


12
@mikeservわかりました。シェルglobは2.48倍高速です。time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'= 3.18s vs time bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'= 1.28s
パトリック

28
最新の更新に関しては、コードが機能していると判断する際に視覚的な出力に依存するのをやめてください。出力を実際のプログラムに渡し、プログラムにファイルの操作を試行させます。これがstat、各ファイルが存在することを実際にチェックするため、私が答えで使用した理由です。sed事の一番下にあるあなたのビットは機能しません。
パトリック

57
真面目なことはできません。あなたの質問が説明するすべてのフープをジャンプすることlsは、最初の場所で単に解析しないよりも簡単または簡単、または何らかの方法で優れているのでしょうか?あなたが説明していることは非常に難しいです。すべてを理解するために分解する必要があり、私は比較的有能なユーザーです。平均的なジョーがこのようなものに対処できるとは期待できないでしょう。
テルドン

46
質問を使用して引数を選択する場合は-1。ls出力の解析が間違っている理由はすべて、元のリンク(および他の多くの場所)で十分に説明されています。OPがそれを理解するのを助けを求めているなら、この質問は合理的でしたが、代わりにOPは単に彼の間違った使い方がOKであることを証明しようとしています。
R ..

14
@mikeservそれだけではありませんparsing ls is bad。こうfor something in $(command)して正確な結果を得るために、単語分割に頼ることは、大多数のために悪いですcommand's、単純な出力を持ちません。
BroSlow

回答:


184

私はこれをまったく納得していません、議論のために、十分な努力をする準備ができていればls、「敵」に直面しても、信頼できる出力を解析できます。あなたが書いたコードを知っており、それを破るために設計されたファイル名を意図的に選択しています。

それができたとしても、それは悪い考えです。

Bourneシェルは良い言語ではありません。極端な移植性が他の要素(例autoconf)よりも重要でない限り、複雑なものには使用しないでください。

出力の解析がlsシェルスクリプトの最小の抵抗のパスのように見えるという問題に直面している場合、それはあなたがしていることはシェルにとって複雑すぎて、全体を書き直す必要があることを強く示していると主張しますPerlまたはPython。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))

5
これはいい。それfor in | for inは再帰の話ですか?よく分かりません。たとえそれが複数であってはいけませんよね?これがこれまでのところ私にとって理にかなっている唯一の答えです。
mikeserv

10
再帰なし、単にネストされたforループ。os.walk舞台裏でいくつかの深刻なリフティングを行っていますが、内部での作業方法lsfind作業について心配する必要がある以上、心配する必要はありません。
zwol

6
技術的にos.walkは、ジェネレーターオブジェクトを返します。ジェネレータは、Pythonの遅延リストです。外側のforループが繰り返されるたびに、ジェネレーターが呼び出され、別のサブディレクトリの内容を「生成」します。Perlの同等の機能はFile::Find、それが役立つ場合です。
zwol

6
あなたは私があなたが批判している文書とパトリックとテルドンの答えに100%同意することを知っているべきです。私の答えは、出力の解析を回避するための追加の独立した理由を提供することを意図していましたls
zwol

19
これは非常に紛らわしいです。シェルは良いプログラミング言語ではありませんが、それはプログラミング言語ではないからです。スクリプト言語です。そして、それは優れたスクリプト言語です。
マイルルーティング

178

そのリンクは、情報が完全に正確であり、非常に長い間存在しているため、頻繁に参照されます。


ls印刷できない文字をグロブ文字で置き換えますyesですが、これらの文字は実際のファイル名には含まれていません。なぜこれが重要なのですか?2つの理由:

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

例えば:

$ 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
./ab
./ab

残りのファイルはどこに行きましたか?

代わりにこれを試してみましょう:

$ 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
./ab
./ab
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
./ab
./ab
./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
./ab
./ab
./a b
./a
b
./a  b
./ab
./ab
./a b
./ab
./ab
./a b
./a
b
./a b
./ab
./ab
./a b
./a
b

シェルグロブでは、完全に機能します。

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

bashがこのように動作する理由は、回答の冒頭で述べたポイントの1つ、「ファイルglobは複数のファイルに一致する可能性がある」に戻ります。

lsa?b複数のファイルに対して同じグロブ()を返しているため、このグロブを展開するたびに、それに一致するすべてのファイルが取得されます。


使用していたファイルのリストを再作成する方法:

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文字です。


5
@mikeservは実際、彼のソリューションはglobを返しません。答えを更新して、その点を明確にしました。
パトリック

18
「残りではない」?それは一貫性のない動作であり、予期しない結果です。それが理由ではないのですか
パトリック

11
@mikeservあなたの質問に対する私のコメントは見ませんでしたか?シェルグロビングはの2.5倍高速ですls。また、コードが機能しないため、コードをテストすることをリクエストしました。zshはこれとどのような関係がありますか?
パトリック

27
@mikeservいいえ、bashにも適用されます。あなたが私が言っていることを聞いていないので、私はこの質問で終わりました。
パトリック

7
あなたは何を知っていますか、私はこの答えに賛成し、私がそれが言うすべてに同意することを私の中で明確にすると思います。;-)
zwol

54

少し単純化してみましょう。

$ touch a$'\n'b a$'\t'b 'a b'
$ ls
a b  a?b  a?b
$ IFS="
"
$ set -- $(ls -1q | uniq)
$ echo "Total files in shell array: $#"
Total files in shell array: 4

見る?それはすでにそこに間違っています。3つのファイルがありますが、bashは4を報告しています。これは、に渡される前にシェルによって展開されるset生成されたグロブが与えられてlsいるためsetです。それがあなたが得る理由です:

$ for x ; do
>     printf 'File #%d: %s\n' $((i=$i+1)) "$x"
> done
File #1: a b
File #2: a b
File #3: a    b
File #4: a
b

または、必要に応じて:

$ printf ./%s\\0 "$@" |
> od -A n -c -w1 |
> sed -n '/ \{1,3\}/s///;H
> /\\0/{g;s///;s/\n//gp;s/.*//;h}'
./a b
./a b
./a\tb
./a\nb

上記はで実行されましたbash 4.2.45


2
これを支持しました。あなた自身のコードがあなたに噛みつくのを見るのは良いことです。しかし、私がそれを間違ったからといって、それが正しくできないというわけではありません。今朝あなたにそれをする非常に簡単な方法を示しましたls -1qRi | grep -o '^ *[0-9]*'-それはls出力を解析する人です、そしてそれは私がiノード番号のリストを得るために知っている最も速くて最良の方法です。
mikeserv

38
@mikeserv:時間と忍耐があればそれを正しく行うことができます。しかし、事実は、本質的にエラーが発生しやすいということです。あなた自身が間違っています。そのメリットについて議論しながら! それのために戦っている一人でも正しくそれをしないなら、それはそれに対する大きなストライキです。そして、可能性としては、おそらくあなたはそれを正しくする前に、それを間違って取得するためにより多くの時間を費やすでしょう。私はあなたについては知らないが、ほとんどの人は同じコード行で長い間いじくり回すよりも自分の時間を使ったほうがよい。
cHao

@cHao-私はそのメリットを主張しませんでした-私はその宣伝に抗議しました。
mikeserv

16
@mikeserv:それに対する議論は十分に根拠があり、当然のことです。あなたもそれらが真実であることを示しました。
cHao

1
@cHao-私は同意しません。マントラと知恵の間にはそれほど細かな線はありません。
mikeserv

50

の出力ls -qはまったくグロブではありません。これは、使用しています?「直接表示することはできませんここに文字がある」を意味します。グローブは?、「ここでは任意の文字を使用できます」という意味です。

グロブは、他の特殊文字を持っている(*[]、少なくとも、および内側[]組以上あります)。それらのどれもによって逃げられませんls -q

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

ls -1q出力を処理する場合、グロブのセットがあり、それらを拡張すると、x2回取得するだけでなく、[x]完全に見逃します。グロブとして、文字列としては一致しません。

ls -q 目や端末を狂ったキャラクターから救うためのものであり、シェルにフィードバックできるものを作成するためのものではありません。


42

答えは簡単です。lsあなたの特別な場合は、考えられる利益よりも重要です。ls出力を解析しない場合、これらの特殊なケースは回避できます。

ここでのマントラは、ユーザーファイルシステム決して信頼ませんユーザー入力決して信頼ないことに相当します)。100%の確実性で常に機能する方法がある場合ls、同じ方法で確実性が低くても、それが望ましい方法になります。技術的な詳細は、TerdonPatrickが広範囲にカバーしているので、説明しません。ls私の仕事/名声が高い重要な(そしておそらく高価な)トランザクションで使用するリスクがあるため、回避できる場合は、不確実性の程度のないソリューションを好むことを知っています。

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


33

人々が言う理由決してそれは絶対に確実に正しく行うことができないので、何かをしていないが、必ずしもではありません。そうすることができるかもしれませんが、それはより複雑で、空間的にも時間的にも非効率的かもしれません。たとえば、「x86アセンブリで大規模なeコマースバックエンドを構築しないでください」と言ってもまったく問題ありません。

さて、手元の問題について:既に説明したように、lsを解析して正しい結果を出すソリューションを作成できます。したがって、正確性は問題になりません。

もっと複雑ですか?はい、しかしヘルパー関数の背後にそれを隠すことができます。

だから今効率に:

スペース効率:ソリューションはuniq重複の除去に依存しているため、結果を遅延して生成することはできません。だから、どちらかのO(1)O(n)またはその両方を持っていますO(n)

時間効率:最良の場合uniqはハッシュマップアプローチを使用しているため、おそらくであるにもかかわらず、調達されるO(n)要素の数にアルゴリズムが残っています。O(n log n)

さて、本当の問題:アルゴリズムの見た目はそれほど悪くありませんが、nの要素ではなく調達した要素を使用するように本当に注意しました。それは大きな違いを生むからです。あなたはファイル持っていると言う\n\nためのグロブになります??ので、リスト内のすべての2文字のファイルと一致します。面白い\n\rことに??、2文字のファイルすべてを生成し、返す別のファイルがある場合は、これがどこにあるのかを確認してください。線形の振る舞いの代わりに指数関数的な振る舞いは確かに「最悪の実行時の振る舞い」として適格です。これは、実用的なアルゴリズムと、理論上のCSジャーナルで論文を書くアルゴリズムとの違いです。

誰もが例を愛していますか?さあ。「test」というフォルダーを作成し、フォルダーがある同じディレクトリでこのpythonスクリプトを使用します。

#!/usr/bin/env python3
import itertools
dir = "test/"
filename_length = 3
options = "\a\b\t\n\v\f\r"

for filename in itertools.product(options, repeat=filename_length):
        open(dir + ''.join(filename), "a").close()

これが行うのは、長さ3のすべての製品を7文字で生成することだけです。高校の数学では、343ファイルにする必要があることがわかります。まあ、それは本当に素早く印刷されるはずなので、見てみましょう:

time for f in *; do stat --format='%n' "./$f" >/dev/null; done
real    0m0.508s
user    0m0.051s
sys 0m0.480s

さあ、最初の解決策を試してみましょう。

eval set -- $(ls -1qrR ././ | tr ' ' '?' |
sed -e '\|^\(\.\{,1\}\)/\.\(/.*\):|{' -e \
        's//\1\2/;\|/$|!s|.*|&/|;h;s/.*//;b}' -e \
        '/..*/!d;G;s/\(.*\)\n\(.*\)/\2\1/' -e \
        "s/'/'\\\''/g;s/.*/'&'/;s/?/'[\"?\$IFS\"]'/g" |
uniq)

ここでは、Linuxミント16で動作するようにしています(この方法の使いやすさについては多くのことを述べていると思います)。

とにかく、上記は結果を取得した後にフィルタリングするだけなので、以前のソリューションは少なくとも後者と同じくらい速いはずです(その中にはinodeのトリックはありませんが、それらは信頼できないので正確さをあきらめます)。

だから今どのくらい

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

取る?よくわかりませんが、343 ^ 343個のファイル名をチェックするのには時間がかかります。


6
もちろん、別の回答のコメントで言及されているように、「... lsを解析して正しい結果を与えるソリューションを作成できることを実証しました...」という文は実際には正しくありません。
ワイルドカード

26

対処されたOPの表明された意図

序文および元の回答の根拠 2015-05-18に更新

mikeserv(OP)は、彼の質問に、最新のアップデートで次のように述べた。「私はないそれ恥考える私が最初にそれにもかかわらず誤報の原因を指摘して、この質問をし、そして、残念ながら、ここで最もupvoted答えが大部分で誤解を招く恐れがあります。 」

まあいいよ; 質問を読み直したときにそれを見つけるためだけに、自分の意味を説明する方法を見つけよとして非常に多くの時間を費やしたのは、むしろ恥ずべきことでした。この質問は最終的に「回答ではなく[生成]ディスカッション」‡になり、ブログ投稿でも長くなると思われる〜18Kのテキスト(質問のみ、明確にするために)に重くなりました。

ただし、StackExchangeはソープボックスではなく、ブログでもありません。ただし、実際には、少なくとも両方のビットとして使用しています。人々は、人々の実際の質問に答えるのではなく、あなたの「To-Point-Out」に答えるのに多くの時間を費やすことになりました。この時点で、OPが質問となることさえ意図されていないと明示的に述べていることを考えると、私たちのフォーマットに適さないとして質問にフラグを立てます。

この時点で、私の答えがそのポイントに対するものであったかどうかはわかりません。おそらくそうではありませんが、それはあなたの質問のいくつかに向けられたものであり、おそらく他の誰かに対する有用な答えになるかもしれません。初心者は心を動かします。経験を積むと、「しない」の一部が「時々する」に変わります。:)

原則として...

残りの粗いエッジはご容赦ください。私はすでにこれにあまりにも多くの時間を費やしました... OPを直接引用するのではなく(当初意図したとおり)、要約して言い換えようとします。

[元の回答から大幅に改訂]
検討すると、OPが回答した質問に重点を置いているという強調を誤解したと思います。しかし、対処れたポイント提起され、私はそれらがポイントであり、初心者へのアドバイスに関して他のコンテキストで提起された問題に対処すると信じているので、私は答えをほとんどそのまま残しました。

元の投稿は、いくつかの方法で、さまざまな記事が「ls出力を解析しない」または「出力を解析しないでください」などのアドバイスをした理由を尋ねましたls

この問題に対する私の提案する解決策は、この種のステートメントのインスタンスは、イディオムの単なる例であり、わずかに異なる方法で表現されており、絶対量指定子が命令型とペアになっていることです。 «[するべき]常にY»、«[するべきではない] Z»]は、特に絶対的な真理として意図されるのではなく、主題に新しいものに与えられるとき、一般的なルールまたはガイドラインとして使用されることを意図したステートメントを形成します。それにもかかわらず、これらのステートメントの明らかな形式

新しい主題を学び始めているとき、そうでなければ他のことをする必要があるかもしれない理由を十分に理解していない限り、経験豊富な人からの指導がない限り、例外なく受け入れられた一般的な規則に従うことをお勧めします自分自身。スキルと経験が上がると、特定の状況でルールがいつ適用されるかをさらに判断できるようになります。かなりのレベルの経験を積めば、そもそも一般的なルールの背後にある理由を理解できるでしょう。その時点で、ルールの背後にある理由がどのレベルに適用されるかについて判断を始めることができます。その状況、そしておそらく最も重要な懸念があるかどうかについて。

そして、それはおそらく、専門家が「ルール」に違反して物事を行うことを選択するときです。しかし、だからといって「ルール」が少なくなるわけではありません。

そして、手元のトピックへ:私の見解では、専門家が完全に叩かれずにこのルールに違反する可能性があるからといって、初心者に「時々」と言うことを正当化する方法はありません。ls出力を解析しても大丈夫です。なぜならそうではないからです。または、少なくとも、確かに初心者がそうするのは正しくありません。

ポーンは常に中央に置きます。オープニングでワンピース、ワンムーブ; 最も早い機会に城。司教の前の騎士; 縁の騎士は厳しいです。そして、最後まで計算が見えることを常に確認してください!(すみません、疲れました。チェスのStackExchangeです。)

ルール、破られることを意味しますか?

初心者を対象とする、または読む可能性が高い主題に関する記事を読むとき、多くの場合、次のようなものが表示されます。

  • 「X は絶対にしないください。」
  • 「Qをしないでください!」
  • 「Zをしないでください。」
  • 「常にYをすべきです!」
  • 「C、なんであれ。」

これらの記述は確かに絶対的で時代を超越した規則を述べているように見えますが、そうではありません。代わりに、これは一般的なルール(別名「ガイドライン」、「経験則」、「基本」など)を述べる方法であり、少なくともおそらくそれらの記事を読んでいる初心者のためにそれらを述べる適切な方法の1つです。しかし、それらが絶対的であると述べられているからといって、規則は確かに専門家や専門家を拘束するものではありません。特定のクラフトの問題。]

これらの規則は、専門家が複雑または微妙な問題にどのように対処するかを明らかにするものではありません。または、そもそもルールにつながった懸念が単に当てはまらない場合。専門家は、特定の状況では意味をなさないことがわかっているルールを単に破る(または恐れてはならない!)ことを恐れていません。専門家は、クラフトのさまざまなリスクと懸念のバランスを取ることに常に取り組んでおり、さまざまな要因のバランスを取る必要があり、従うべきルールの表だけに頼ることができないため、頻繁に判断を使用してこうした種類のルールを破ることを選択する必要があります。テイクGoto例として:彼らは有害であるかどうかに長い、定期的、議論が行われています。(うん、いない今までのgotoを使う; D)

モーダル命題

少なくとも英語では、そして他の多くの言語では一般的なルールの奇妙な特徴は、モーダル命題と同じ形式で述べられているが、分野の専門家は状況に応じて、適切な場合にはルールに違反することがわかっています。したがって、明らかに、これらのステートメントは、モーダルロジックの同じステートメントと同等であることを意図していません。

これが、彼らが単に慣用的なものでなければならないと言う理由です。これらのルールは、真に「決して」または「常に」の状況ではなく、通常、幅広い状況で適切になる傾向がある一般的なガイドラインを成文化するのに役立ちます。初心者が正当な理由なしにそれらに反対することを選択するよりも良い結果。ルールに違反した場合の誤った選択に伴う完全な失敗ではなく、単に標準以下の結果につながるルールを成文化します。

したがって、一般的なルールは、表面上にあるように見える絶対的なモーダル命題ではなく、代わりに、次のような標準的な定型文を使用してルールを提供する簡単な方法です。

特定のケースでこのガイドラインが間違っていることを伝え、自分が正しいことを証明する能力がない場合は、$ {RULE}

もちろんls、$ {RULE}の代わりに「出力を解析しない」に置き換えることもできます。:)

そうそう!出力の解析についてはどうlsですか?

まあ、だから、すべてを考えると...私はこのルールが良いものであることはかなり明らかだと思う。まず、上記で説明したように、実際のルールは慣用的であると理解する必要があります...

しかし、さらに、シェルスクリプトが破損する可能性があるかどうかを特定のケースで知るためには、シェルスクリプトを非常に上手に使用する必要があるだけではありません。また、テストでそれを破ろうとしているときに間違ったことを伝えるには、同じくらいのスキルが必要です!そして、私はそのような記事(«はの出力解析しないようにアドバイスを与える可能性の高い聴衆の非常に大多数のことを自信を持って言うls!の»)は、これらのことを行うことができない、そのようなスキルを持っているものの可能性が高いことを実現します彼らは自分でそれを理解し、とにかくルールを無視します。

しかし... ...この質問を見てください。そして、おそらくスキルを持っている人でさえ、そうするのは悪いコールだと思ったのです。そして、質問の著者が現在の最良の例にたどり着くためにどれだけの労力を費やしましたか!私は、99%の人々がそれを難し​​くし、潜在的に非常に悪い結果をもたらすという困難な問題についてあなたを保証します!決定された方法が良い方法であると判明したとしても; lsIT (または別の)解析のアイデアがIT /開発者全体に採用され、多くのテスト(特に時間のテスト)に耐え、最終的に「一般的な手法」のステータスに達するまでは、多くの人がそれを試して、間違ってしまうかもしれません...悲惨な結果をもたらします。

それで、私は最後にもう一度繰り返します…。特にこの場合それが「出力を解析しないls!」それは明らかにそれを表現する正しい方法です。

[2014-05-18更新:OPからのコメントに回答するための回答(上記)の理由を明確化。次の追加は、昨日の質問に対するOPの追加に対応しています]

[2014-11-10更新:ヘッダーの追加とコンテンツの再編成/リファクタリング。また、再フォーマット、言い直し、明確化、および「...簡潔化」...私はこれを単にクリーンアップすることを意図しましたが、それは少し手直しになりました。私は残念な状態でそれを残していたので、私は主にそれをいくつかの順序を与えようとしました。最初のセクションをほぼそのままにしておくことが重要だと感じました。そのため、2つの小さな変更のみが行われ、冗長な「しかし」削除され、「それ」が強調されました。

†私はもともとこれを私のオリジナルの説明としてのみ意図していました。しかし、反映時に他の追加を決定しました

‡ 投稿に関するガイドラインについては、https: //unix.stackexchange.com/tourを参照してください


2
決して慣用的ではありません。これは何に対する答えでもありません。
mikeserv

1
うーん。まあ、この答えが満足のいくものかどうかは知りませんでしたが、議論の余地があるとはまったく思いませんでした。そして、私は「決して」がそれ自体慣用的であるとは主張しませんでした。しかし、「Xをしないでください!」は慣用的な使用です。「解析しない/解析しない」ことを示す2つの一般的なケースがありますls。正しいアドバイスです。1. 出力を解析する可能性のあるすべてのユースケースにls、何らかの方法で優れた別の利用可能なソリューションがあることを(満足できるように)実証します。2.引用されたケースでは、文が文字通りのものではないことを示す。
shelleybutterfly

あなたの質問をもう一度見ると、あなたはあなたの分析によくある「決して...」ではなく、最初に「してはいけない...」に言及していることがわかります。その点についても明確にします。この時点ですでに最初のタイプのソリューションがありますが、これは明らかに満足できるように実証/説明されているので、あまり掘り下げません。しかし、私の答えを少し明確にしてみます。私が言うように、私は物議を醸す(または対立する!)のではなく、それらのステートメントが一般的にどのように意図されているかを指摘しようとしました。
shelleybutterfly

1
その投稿を整理する必要があります。それでも、決してありませんではないフレーズそれへの正しい方法。それは、人々が聞きしたいが、他の人に教えないように資格だと思うことを少しばかげいけない -ちょうどあなたはそれが動作し、なぜだろうと思ういけないことを伝えていますが、仕事となぜかわかりません。lsはコンピューターユーティリティです- コンピューターの出力
mikeserv

1
まあ、私はダウンボートを逆にしたのは、少なくとも、あなたが正しいことについては正しいからです。今晩か明日、それをきれいにしようとします。私の考えでは、ほとんどのコード例を推測の答えに移します。しかし、それは、私に関する限り、そのよく引用されたブログ投稿の不正確さを許しません。少なくともPOSIX仕様を引用した後は、少なくともbashマニュアルを引用するのをやめてほしいと思います...
mikeserv

16

ls特定のケースでの出力を解析することは可能ですか?承知しました。ディレクトリからiノード番号のリストを抽出するというアイデアは良い例です-実装がlsサポートしていることを知っている場合-q、各ファイルが正確に1行の出力を生成し、必要なのはiノード番号だけで、それらを解析しますls -Rai1q出力は確かに可能な解決策です。もちろん、著者が「lsの出力を解析しない」などのアドバイスを見たことがない場合、改行を含むファイル名についてはおそらく考えず、結果として 'q'を省略します。そのような場合、コードは微妙に壊れます。そのため、構文解析lsの出力が妥当な場合でも、このアドバイスは役に立ちます。

より広範なポイントは最大のディレクトリ内のファイル、または何ディレクトリの中で最も最近変更されたファイルです何のスクリプトをシェルに初心者は、(例えば)スクリプトフィギュアアウトを持ってしようとしたとき、ということで、彼の最初の本能は、解析することですls年代を出力- ls初心者が学習する最初のコマンドの1つであるため、理解できます。

残念ながら、その本能は間違っており、そのアプローチは壊れています。さらに残念なことに、それは微妙に壊れています-それはほとんどの場合動作しますが、コードの知識を持つ誰かによって悪用される可能性のあるエッジケースでは失敗します。

初心者はls -s | sort -n | tail -n 1 | awk '{print $2}'、ディレクトリ内で最大のファイルを取得する方法と考えるかもしれません。そして、名前にスペースを含むファイルができるまで機能します。

OK、ls -s | sort -n | tail -n 1 | sed 's/[^ ]* *[0-9]* *//'じゃあどう?名前に改行を含むファイルが作成されるまで正常に機能します。

ファイル名に改行がある場合、引数に追加-qするとls役立ちますか?ファイル名の同じ場所に印刷不可能な文字を含む2つの異なるファイルがあり、そのls出力が最大のものを区別できないまでは、そのように見えるかもしれません。さらに悪いことに、「?」を展開するために、彼はおそらく彼のシェルに頼りますeval-たとえば、

foo`/tmp/malicious_script`bar

--quoting-style=shell(あなたがあれば助けてlsも、それをサポートしていますか)?いいえ、まだ表示されますか?印刷できない文字の場合、複数の一致のどれが最大であったかはまだあいまいです。 --quoting-style=literal?いや、同じ。 --quoting-style=localeまたは--quoting-style=c、最大のファイルの名前を明確に印刷する必要がある場合に役立つかもしれませんが、その後ファイルで何かを行う必要がある場合はおそらくそうではありません-引用を取り消して実際のファイル名に戻すためのコードの束になるでしょうたとえば、gzipに渡すことができます。

そして、すべてのその作業の終わりに、たとえ彼が持っているものがすべての可能なファイル名に対して安全で正しいとしても、それは判読できず、維持できず、Pythonまたはperlまたはrubyではるかに簡単に、安全に、そして読みやすく実行できます。

または、他のシェルツールを使用することもできます-私の頭上で、これはトリックを行うべきだと思います:

find . -type f -printf "%s %f\0" | sort -nz | awk 'BEGIN{RS="\0"} END{sub(/[0-9]* /, "", $0); print}'

そして、少なくとも現状のままのポータブルでなければなりません--quoting-style


サイズについては本当です-私が試した場合、おそらくそれを行うことができます-私はすべきですか?私はちょっと疲れているか、この全体-あなたは答えが好きです。あなたは言うことができないできないいけない、または決して言わないが、実際には多分なぜないのか、他に匹敵する方法の例を挙げているので -ありがとう。
mikeserv

あなたが試みたなら、あなたはそれがあなたが思っているよりもはるかに難しいことに気付くでしょう。だから、はい、試してみることをお勧めします。考えられる限り、壊れてしまうようなファイル名を付け続けたいです。:)
godlygeek

コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
テルドン

@mikeservとgodlygeek、このコメントスレッドをchatに移動しました。コメントでこのような長い議論をしないでください、それがチャットの目的です。
テルドン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.