为什么*不*解析`ls`(以及怎么做)?


204

我一直看到答案引用此链接,并明确声明“请勿解析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单词“ a”“ newline”之间的字符变成了一个问号 ...

...

如果只想遍历当前目录中的所有文件,请使用for循环和glob:

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

当返回包含shell glob 的文件名列表时,作者称其为garbling filenames ,然后建议使用shell glob来检索文件列表!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- (数字一位。)强制输出为每行一个条目。

混响并非没有其自身的问题- ?匹配任何字符,因此?列表中的多个匹配结果将多次匹配同一文件。这很容易处理。

尽管怎么做并不是重点-毕竟它不需要做很多事,并在下面进行了证明-我对为什么不做感兴趣。我认为,对该问题的最佳答案已被接受。我建议您尝试将重点更多地放在告诉人们他们可以做什么而不是他们不能做什么上我认为,至少被证明是错误的可能性要小得多。

但是,为什么还要尝试呢?诚然,我的主要动机是别人不断告诉我我做不到。我非常清楚,ls只要您知道要查找的内容,输出就可以像您希望的那样定期且可预测。错误的信息使我比做大多数事情更受困扰。

事实是,尽管帕特里克和Wumpus Q. Wumbley的答案都值得注意(尽管后者的用法很棒),但我认为答案中的大多数信息都是正确的-Shell Glob 都更易于使用并且通常在搜索当前目录方面比解析更有效ls。但是,至少在我看来,它们并没有足够的理由来证明传播以上文章中引用的错误信息,也不是可以接受的“ 从不解析ls ”的理由

请注意,帕特里克答案的不一致结果主要是他使用zshthen 的结果bashzsh-默认情况下-不以可移植的方式$(用单词拆分命令替换)结果。因此,当他问其余文件放在哪里时?这个问题的答案是您的贝壳吃掉了它们。这就是为什么SH_WORD_SPLIT在使用zsh和处理可移植Shell代码时需要设置变量的原因。我认为他没有在答复中指出这一点是严重的误导。

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或字母数字在shell水珠则字符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选项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]*'

那只是返回inode号-这是另一个方便的POSIX指定的选项。


12
@mikeserv好吧,我做到了。Shell 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
Patrick

28
关于您的最新更新,请停止依赖视觉输出来确定您的代码是否有效。将您的输出传递到实际程序,并让该程序尝试对文件执行操作。这就是我stat在答案中使用的原因,因为它实际上检查每个文件是否存在。您对sed事物的了解不大。
帕特里克

57
你不能当真 与不ls首先解析相比,如何轻松解决问题所描述的所有困难呢?什么你所描述的是非常困难的。我需要解构它才能理解所有内容,而且我是一个相对称职的用户。您不可能期望普通的Joe能够处理这样的事情。
terdon

46
-1用于使用问题选择参数。解析ls输出错误的所有原因在原始链接(以及很多其他地方)中都得到了很好的阐述。如果OP寻求帮助来理解它,那么这个问题就很合理了,但是OP只是试图证明自己的错误用法是可以的。
R ..

14
@mikeserv不仅如此parsing ls is bad。这样做for something in $(command)并依托字拆分得到准确的结果是不好的绝大多数command's不具有简单的输出。
BroSlow 2014年

Answers:


184

我一点都不相信这一点,但是为了争辩,让我们假设,即使您准备采取足够的努力,即使面对“对手” ,您也可以ls可靠地解析输出结果。知道您编写的代码,并有意选择旨在破坏它的文件名。

即使您可以做到,但这仍然不是一个好主意

伯恩壳不是一门好语言。除非极端的可移植性比其他任何因素(例如autoconf)都重要,否则都不应将其用于任何复杂的情况。

我声称,如果您遇到解析lsshell脚本的阻力最小的路径的问题,那就有力地表明您所做的一切对于shell来说太复杂了,您应该将整个过程重写为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)直接使用结果。

同样重要的是,与您所写的内容形成鲜明对比的是,从现在起六个月后,它仍然有意义,并且当您需要做一些稍有不同的操作时,可以轻松进行修改。举例说明,假设您发现需要排除点文件和编辑器备份,并需要按基名称按字母顺序处理所有内容:

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 2014年

6
从技术上讲,os.walk返回一个generator对象。生成器是Python的惰性列表版本。每当外部for循环迭代时,都会调用生成器并“屈服”另一个子目录的内容。File::Find如果有帮助,Perl中的等效功能是。
zwol 2014年

6
您应该意识到,我100%同意您批评的文档以及Patrick和Terdon的回答。我的回答是为了提供一个额外的独立原因,以避免解析ls输出。
zwol

19
这是非常误导的。Shell不是一种好的编程语言,只是因为它不是一种编程语言。这是一种脚本语言。这是一种很好的脚本语言。
Miles Rout

178

该链接被引用很多,因为该信息是完全准确的,并且已经存在了很长时间。


ls用全局字符“ yes”替换不可打印字符,但是这些字符不在实际文件名中。为什么这么重要?2个原因:

  1. 如果将该文件名传递给程序,则该文件名实际上不存在。它必须扩展glob以获得真实的文件名。
  2. 文件文件可能匹配多个文件。

例如:

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

请注意,我们有2个看起来完全一样的文件。如果它们都表示为,您将如何区分它们a?b


当ls返回包含shell glob的文件名列表时,作者称其为garbling filenames,然后建议使用shell glob来检索文件列表!

这里有区别。如图所示,当您恢复一个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
./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

现在让我们使用一个实际的glob:

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

用bash

上面的示例是我的普通shell 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

使用shell glob,它可以很好地工作:

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

bash如此行为的原因可以追溯到我在回答开始时提出的观点之一:“文件glob可能匹配多个文件”。

lsa?b针对多个文件返回相同的glob(),因此,每次我们扩展该glob时,我们都会获得与之匹配的每个文件。


如何重新创建我正在使用的文件列表:

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

十六进制代码是UTF-8 NBSP字符。


5
@mikeserv实际上他的解决方案没有返回问题。我刚刚更新了我的答案以澄清这一点。
Patrick

18
“没有休息”吗?这是不一致的行为,并且结果出乎意料,这不是原因吗?
Patrick

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给了glob所生成的glob,ls由shell对其进行了扩展,然后再传递给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输出,人工,这是我知道的获取inode编号列表的最快,最好的方法。
mikeserv

38
@mikeserv:如果您有时间和耐心,可以做对。但是事实是,它天生就是容易出错的。你自己弄错了。在争论其优点时!如果连为之奋斗的人也未能正确做到这一点, 那就是对它的巨大打击。很有可能,在正确之前,您可能会花费更多的时间将其弄错。我对您不屑一顾,但是大多数人比他们花更多的时间做事情比花很多时间使用相同的代码行更好。
cHao 2014年

@ cHao-我没有争论它的优点-我抗议它的宣传。
mikeserv

16
@mikeserv:反对它的论点是有根据的,是当之无愧的。即使您已经证明它们是真实的。
cHao 2014年

1
@cHao-我不同意。口头禅和智慧之间的界限不太细。
mikeserv

50

的输出ls -q根本不是glob。它用来?表示“这里有一个字符不能直接显示”。球形?表示“此处允许使用任何字符”。

水珠有其它特殊字符(*[]至少,和内侧[]对有以上)。这些都没有被逃脱ls -q

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

如果您处理ls -1q输出,则会有一组glob并将其扩展,不仅会得到x两次,而且会[x]完全错过。作为一个整体,它与字符串本身不匹配。

ls -q 旨在使您的眼睛和/或终端免受疯狂角色的侵扰,而不是产生可反馈给外壳的东西。


42

答案很简单:特殊情况下ls您必须处理任何可能带来的好处。如果不解析ls输出,则可以避免这些特殊情况。

这里的口头禅是永不信任用户文件系统(等效于永不信任用户输入)。如果有一种方法始终可以100%确定性地起作用,那么即使该方法ls相同但确定性较低,也应该是您首选的方法。我将不讨论技术细节,因为这些细节已被terdonPatrick广泛涵盖。我知道,由于ls在重要工作(可能是昂贵事务)中使用工作/声望很高的交易存在风险,因此,如果可以避免的话,我将首选没有不确定性等级的任何解决方案。

我知道有些人比确定性更愿意冒险,但是我已经提交了错误报告


33

人们说从不做某事的原因不一定是因为绝对不能正确地做某事。我们也许可以这样做,但是它在空间或时间上都可能更复杂,效率更低。例如,最好说“永远不要在x86汇编中构建大型的电子商务后端”。

现在就来解决这个问题:正如您所演示的,您可以创建一个解析ls并给出正确结果的解决方案-因此正确性不是问题。

更复杂吗?是的,但是我们可以将其隐藏在辅助函数的后面。

所以现在要提高效率:

节省空间:您的解决方案依赖于uniq筛选出重复项,因此我们不能懒惰地生成结果。因此,无论是O(1)vs O(n)还是两者都有O(n)

时间效率:最好的情况是uniq使用哈希图方法,因此我们仍然可以O(n)采购的元素数量上使用一种算法,尽管它是O(n log n)

现在真正的问题是:虽然您的算法看起来还不错,但我还是非常谨慎地使用采购的元素而不是n的元素。因为那确实有很大的不同。假设您有一个文件\n\n,该文件将导致glob,??因此请匹配清单中的每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()

唯一要做的就是为7个字符生成所有长度为3的乘积。高中数学告诉我们,应该是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 Mint 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

迈克韦斯(OP)在他的问题的最新更新中表示:“ 尽管我首先问这个问题以指出错误信息的来源,但我确实认为这是很可耻的,不幸的是,这里最被推崇的答案在很大程度上是误导性的。 ”

哦,那好吧; 我感到很遗憾,因为我花了很多时间试图弄清楚如何解释我的意思,而在重新阅读问题时才发现这一点。这个问题最终导致“ [引起讨论,而不是回答”” ,最终导致大约18K的文本(仅就问题而言,就是为了清楚起见),甚至对于博客帖子来说也很长。

但是StackExchange不是您的肥皂盒,也不是您的博客。但是,实际上,您至少将其用作了两者。人们最终花费大量时间回答您的“ To-Point-Out”,而不是回答人们的实际问题。在这一点上,我将标记该问题不适合我们的格式,因为OP明确表示它根本不打算成为一个问题。

在这一点上,我不确定我的答案是否正确。可能不是,但这是针对您的一些问题,也许对其他人可能是一个有用的答案;初学者会振作起来,一旦经验丰富,其中一些“不做”就会变成“有时做”。:)

作为基本规则...

请原谅剩余的粗糙边缘;我已经在此上花费了太多时间...而不是直接引用OP(如最初的意图),我将尝试进行总结和解释。

[考虑到我的原始回答后,大部分内容都进行了重新设计]
,我认为我误解了OP对我回答的问题的重视;但是,提出的要点已经提出,并且我留下的答案基本上是完整的,因为我相信这些答案是对的,并且可以解决我在其他情况下提出的有关初学者的建议的问题。

原始帖子以几种方式询问为什么各种文章都提供了建议,例如“不要解析ls输出”或“您不应该解析ls输出”等等。

我对这个问题的建议解决方案是,这种陈述的实例只是一个成语的例子,用稍微不同的方式表达,其中一个绝对量词与一个命令式[例如,不要[永远] X]配对, «[您应该永远是Y”,«[一个应该永远不要Z»]形成旨在用作一般规则或准则的陈述,尤其是当给刚接触主题的人时,而不是作为绝对真理时,尽管有这些陈述的明显形式

当您开始学习新的主题时,除非您对为什么可能需要做其他事情有很好的了解,否则最好只遵循公认的一般规则,除非有经验的人指导下那你自己。随着技能和经验的提高,您将能够进一步确定规则何时以及是否适用于任何特定情况。一旦您掌握了相当多的经验,就可能首先了解通用规则的原因,然后,您可以开始使用自己的判断来决定该规则背后的原因是否适用以及适用于何种水平这种情况,以及是否存在压倒一切的担忧。

那时候专家也许会选择违反“规则”的规定行事。但这并不能使它们成为“规则”。

因此,关于手头的话题:在我看来,仅因为专家可以在不被完全击垮的情况下违反此规则,我看不出有什么方法可以证明对初学者“有时”是可以解析ls输出,因为:不是。或者,至少对于初学者而言,这样做当然是不合适的。

您总是将棋子放在中间。在开幕式中,一招。尽早进行城堡建造;主教前的骑士;边缘的骑士是冷酷的;并始终确保您可以完整地看到您的计算!(糟糕,很累,这是为了国际象棋StackExchange。)

规则,意味着要被打破?

在阅读针对初学者或可能被初学者阅读的主题的文章时,通常会看到以下内容:

  • “你永远不应该做X。”
  • “不要做Q!”
  • “不要做Z。”
  • “一个人应该永远做Y!”
  • “ C,无论如何。”

虽然这些声明似乎在说明绝对和永恒的规则,但事实并非如此。取而代之的是,这是一种陈述一般规则的方法(又称“准则”,“经验法则”,“基础知识”等),至少可以说是为可能正在阅读这些文章的初学者陈述这些规则的一种适当方法。但是,仅因为这些规则被说成是绝对的,所以这些规则当然不会约束专业人士和专家[他们很可能首先总结了这些规则,以此作为记录和传递在处理重复出现时所获得的知识的方式问题。]

这些规则当然不会揭示专家将如何处理一个复杂或细微的问题,例如,这些规则相互冲突。或首先导致该规则的担忧根本不适用。专家不害怕(或不应该害怕!)只是打破他们碰巧知道在特定情况下没有道理的规则。专家们一直在努力平衡其工作中的各种风险和忧虑,并且必须经常使用他们的判断力来选择打破那些规则,必须权衡各种因素并且不能仅仅依靠规则表来遵循。以Goto作为一个例子:有许多人对他们是否有害一个长期的,经常性,辩论。(是的,永远不要使用gotos。

模态命题

通用规则的一个奇怪特征是,至少在英语中,并且我想在许多其他语言中,通用规则以与情态命题相同的形式陈述,但该领域的专家愿意为通用规则给出通用规则。情况下,一直知道他们会在适当的时候违反规则。因此,显然,这些语句并不等同于模态逻辑中的相同语句。

这就是为什么我说他们必须简单地习惯。这些规则并不是真正地成为“从不”或“永远”的情况,它们通常是用来编纂一般指导方针,这些指导方针往往适用于广泛的情况,并且当初学者盲目地遵循它们时,很可能会导致比初学者在没有充分理由的情况下选择与之对抗的结果更好。有时,它们将规则编纂成规则,仅导致不合格的结果,而不是违反规则时伴随错误选择而导致的彻底失败。

因此,一般规则不是表面上出现的绝对模态命题,而是在隐含标准样板的情况下给出规则的一种简便方法,如下所示:

除非您有能力告诉您该准则在特定情况下是不正确的,并且自己证明自己是对的,那么$ {RULE}

当然,在这里您可以用“从不解析ls输出”代替$ {RULE}。:)

哦耶!什么关于解析ls输出?

好吧,鉴于所有这些……我认为很明显,这条规则是一条好规则。首先,如上所述,真正的规则必须理解为惯用的……

但是,不仅如此,您不仅必须非常擅长使用Shell脚本来了解在某些特定情况下是否可以将其破坏。同样,当您试图在测试中打破它时,要告诉您它弄错了,也需要同样多的技巧!而且,我有信心地说,此类文章的绝大多数潜在读者(给出诸如“不要解析ls!! 的输出”之类的建议)不能做那些事情,而那些有这种技巧的人可能会意识到他们自己搞定了,还是无视规则。

但是……只要看看这个问题,甚至那些可能确实有这种技能的人怎么会认为这样做是一个错误的选择;以及问题的作者花了多少精力才了解当前最好的例子!我向您保证这个问题很难解决,那里99%的人都会弄错,而且结果可能非常糟糕!即使确定的方法被证明是一种好的方法,直到它(或另一个)ls解析想法被IT /开发人员整体采纳,经受了很多测试(尤其是时间的考验),最后设法升级为“通用技术”状态时,很多人可能会尝试它,并把它弄错……带来灾难性的后果。

因此,我最后一次要重申……。尤其是在这种情况下就是为什么“ 从不解析ls输出!” 绝对是正确的表达方式。

[2014年5月18日更新:澄清了回应OP意见的答案的理由(上述);以下是回应OP对昨天的问题的补充]

[更新2014-11-10:添加了标头和重组/重构的内容;并且:重新格式化,重新措词,澄清和确定...“简明扼要” ...我本意是简单地进行清理,尽管这样做确实有点重做。我把它放在一个遗憾的状态,所以我主要是试图给它一些命令。我确实觉得重要的是要保持第一部分的完整。因此在那里只有两个小的更改,多余的“但”已删除,并强调了“那”。]

†我原本打算仅以此作为对原件的澄清;但是在反思时决定了其他补充

‡ 有关帖子的准则,请参见https://unix.stackexchange.com/tour


2
从来都不是惯用的。这不能解决任何问题。
mikeserv

1
嗯 好吧,我不知道这个答案是否令人满意,但是我绝对不希望它引起争议。而且,我并没有(意思是)认为“从不” 本身是惯用的。但是“永远不要X!” 是惯用用法。我看到两个可以说明“从不/不解析ls!”的一般情况。正确的建议是:1.证明(令您满意)每个可能解析ls输出的用例都有另一种可用的解决方案,该解决方案在某种程度上没有这样做。2.证明在所引用的情况下,该陈述不是文字陈述。
Shelleybutterfly 2014年

再次查看您的问题,我发现您首先提到“不要...”而不是“从不...”,这在您的分析中很常见,因此我也将对此进行澄清。至此,已经有第一种类型的解决方案了,显然您已经满意地证明/解释了这种解决方案,因此我不会在此进行过多研究。但是,我会尝试澄清一下我的答案:就像我说的那样,我并不是要引起争议(或对抗!),而是要指出这些陈述的一般意图。
Shelleybutterfly 2014年

1
我应该收拾那个职位。不过,从来以词的正确方法。人们以为自己有资格告诉别人从不告诉别人,这有点荒谬。只是告诉他们,您不认为这会起作用,为什么,但是您确实知道会起作用和为什么。ls是计算机实用程序-您可以解析计算机输出。
mikeserv

1
好吧,我撤回了我的不赞成票,因为至少,您对那起举报不对。生病尝试今晚或明天清理。我的想法是,我会将大多数代码示例移到我猜得到的答案。但就我而言,它仍然没有为经常引用的博客文章中的不正确之处辩解。我希望人们完全停止引用bash手册-至少在引用POSIX规范后才开始引用…
mikeserv 2014年

16

ls在某些情况下可以解析输出吗?当然。从目录中提取索引节点编号列表的想法是一个很好的例子-如果您知道实现的ls支持-q,因此每个文件将只产生一行输出,而您所需的就是索引节点编号,并将其解析出来ls -Rai1q输出当然是可能的解决方案。当然,如果作者以前从未听过诸如“从不解析ls的输出”之类的建议,他可能不会考虑其中包含换行符的文件名,因此可能会忽略“ q”,因此在这种情况下,代码将被巧妙地破坏-因此,即使在解析ls输出合理的情况下,此建议仍然有用。

在更广泛的观点是,当一个新手到shell脚本试图对一个剧本弄清楚(例如)什么是目录中最大的文件,或者是在一个目录下的最近修改的文件时,他的第一反应是解析ls的输出-可以理解,因为它ls是新手学习的第一批命令之一。

不幸的是,这种本能是错误的,并且这种方法被打破了。更不幸的是,它被巧妙地破坏了-在大多数情况下都可以使用,但是在某些情况下可能会失败,而这种情况可能会被了解代码的人利用。

新手可能会认为是ls -s | sort -n | tail -n 1 | awk '{print $2}'获取目录中最大文件的一种方法。直到您的文件名中带有空格为止,它都可以正常工作。

好吧,那又如何ls -s | sort -n | tail -n 1 | sed 's/[^ ]* *[0-9]* *//'呢?正常工作,直到您的文件名中包含换行符。

文件名中有换行符时,添加-qls的参数是否有帮助?看起来似乎确实如此,直到您在文件名的同一位置有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更加轻松,安全且可读地完成。

甚至使用其他shell工具-我都想尽办法了:

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

并且至少应尽可能轻便--quoting-style


哦,关于尺寸,确实如此-如果尝试过,我可能可以做到-我应该吗?林有点累了或这件事-我喜欢你的答案,因为你不说不能从不但实际上给的,也许例子,为什么不和可比怎么回事 -谢谢。
mikeserv

我认为,如果您尝试过,就会发现它比您想的要难得多。所以,是的,我建议您尝试一下。只要我能想到,我将很乐意继续为您提供不会损坏的文件名。:)
godlygeek

评论不作进一步讨论;此对话已转移至聊天
terdon

@mikeserv和godlygeek,我已将此评论线程移至聊天。请在评论中不要进行像这样的长时间讨论,这就是聊天的目的。
terdon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.