Existe uma razão pela qual ls não tem uma opção - zero ou -0

37

Essa pergunta foi motivada por perguntas sobre lsa -1opção ' e a tendência recorrente das pessoas de fazer perguntas e respostas que incluem o processamento da saída de ls.

Essa reutilização da saída lsparece compreensível, por exemplo: se você souber classificar uma lista de arquivos, lspoderá usar a saída dessa maneira como entrada para outra coisa.

Se essas perguntas e respostas não incluírem uma referência à lista de nomes de arquivos produzida com nomes de arquivos com bom comportamento (sem caracteres especiais, como espaços e novas linhas), eles são frequentemente comentados por alguém apontando o perigo de a sequência de comandos não funcionar quando houver são arquivos com novas linhas, espaços etc.

find, sorte outros utilitários resolvem o problema de comunicar nomes de arquivos "difíceis", xargspor exemplo , usando uma opção para separar os nomes de arquivo com o caractere / byte NUL que não é um caractere válido no nome do arquivo (o único além de /?) em Sistemas de arquivos Unix / Linux.

Procurei na página de manual lse na saída ls --help(que tem mais opções listadas) e não consegui descobrir que ls(de coreutils) tem uma opção para especificar saída separada por NUL. Ele possui uma -1opção que pode ser interpretada como "nomes de arquivos de saída separados por nova linha" )

P : Há razões técnicas ou filosóficas pelas quais lsnão existe uma opção --zeroou -0que "produza nomes de arquivos separados por NUL"?

Se você fizer algo que apenas exiba os nomes dos arquivos (e não use, por exemplo -l) que possa fazer sentido:

ls -rt -0 | xargs -r0 

Eu poderia estar perdendo algo por que isso não funcionaria, ou existe uma alternativa para este exemplo que eu ignorei e que não é muito mais complicado e / ou obscuro .


Termo aditivo:

Fazer ls -lrt -0provavelmente não faz muito sentido, mas da mesma maneira que find . -ls -print0não, portanto não é um motivo para não fornecer uma opção -0/ -z/ --zero.

Timo
fonte
O mais óbvio é escrever e perguntar ao mantenedor do GNU coreutils quais são seus pensamentos sobre essa opção.
Faheem Mitha
1
ls -rtzdefinitivamente seria útil. Compare a alternativa: superuser.com/a/294164/21402
Tobu

Respostas:

37

ATUALIZAÇÃO (02-02-2014)

Graças à nossa determinação do @ Anthon em acompanhar a falta desse recurso , temos uma razão um pouco mais formal para a falta desse recurso, o que reitera o que expliquei anteriormente:

Re: [PATCH] ls: adding --zero/-z option, including tests

From:      Pádraig Brady
Subject:   Re: [PATCH] ls: adding --zero/-z option, including tests
Date:      Mon, 03 Feb 2014 15:27:31 +0000

Muito obrigado pelo patch. Se fizéssemos isso, essa é a interface que usaríamos. No entanto, ls é realmente uma ferramenta para o consumo direto de um ser humano e, nesse caso, processamento adicional é menos útil. Para processamento posterior, find (1) é mais adequado. Isso está bem descrito na primeira resposta no link acima.

Então, eu teria 70:30 contra adicionar isso.

Minha resposta original


Isso é um pouco da minha opinião pessoal, mas acredito que seja uma decisão de projeto deixar essa mudança de lado ls. Se você perceber que o findcomando possui esta opção:

-print0
      True; print the full file name on the standard output, followed by a 
      null character (instead of the newline character that -print uses).  
      This allows file  names  that  contain  newlines or other types of white 
      space to be correctly interpreted by programs that process the find 
      output.  This option corresponds to the -0 option of xargs.

Ao deixar essa opção de fora, os designers sugeriram que você não deveria usar a lssaída para outra coisa senão o consumo humano. Para processamento a jusante por outras ferramentas, você deve estar usando find.

Maneiras de usar o find

Se você está apenas procurando os métodos alternativos, pode encontrá-los aqui, intitulado: Fazendo corretamente: Um resumo rápido . Nesse link, esses são provavelmente os três padrões mais comuns:

  1. Localização simples -exec; pesado se COMMAND for grande e cria 1 processo / arquivo:
    find . -exec COMMAND... {} \;
  2. Encontre -exec simples com +, mais rápido se vários arquivos estiverem OK para COMMAND:
    find . -exec COMMAND... {} \+
  3. Use find e xargs com separadores \ 0

    (extensões comuns fora do padrão -print0 e -0. Funciona em GNU, * BSDs, busybox)

    find . -print0 | xargs -0 COMMAND

Evidência futura?

Eu encontrei esta postagem do blog de Joey Hess, intitulada: " ls: as opções ausentes ". Um dos comentários interessantes neste post:

A única falta óbvia agora é uma opção -z, que deve fazer com que os nomes dos arquivos de saída sejam NULL terminados para consumo por outros programas. Acho que seria fácil escrever, mas estive extremamente ocupado com o IRL (movendo muitos móveis) e não consegui. Algum comprador para escrevê-lo?

Em pesquisas adicionais, encontrei isso nos logs de confirmação de uma das opções adicionais mencionadas no blog de Joey, " novo formato de saída -j ", de modo que parece que a postagem do blog estava zombando da idéia de adicionar uma -zopção ao ls.

Quanto às outras opções, várias pessoas concordam que -e é quase quase útil, embora nenhum de nós consiga encontrar um motivo para usá-lo. Meu relatório de erro não mencionou que ls -eR é muito buggy. -j é claramente uma piada.

Referências

slm
fonte
Obrigado. Estou ciente das advertências. Não há dúvida sobre o processamento de saída ls é completa sem ter que sair pontas ;-)
Timo
@Timo - Eu sei que sim, eu estava fazendo mais pelos futuros leitores deste Q. Eu vejo você no site, que eles já apareceriam em suas pesquisas agora 8-)
slm
Eu percebi isso, e bom que você fez. Eu deveria ter incluído referências a por que não (pelo menos não até que -0seja implementado) na minha pergunta, a fim de não desviar as pessoas.
Timo
Obviamente, supondo que não exista algo realmente exótico como um '\ n' em um nome de arquivo, ls -1 | tr '\012' '\000'listará os arquivos separados por caracteres NULL.
samiam 02/02
2
Este artigo aborda os problemas dos nomes de arquivos: dwheeler.com/essays/fixing-unix-linux-filenames.html
slm
20

Como as respostas do @ slm vão para as origens e possíveis razões, não repetirei isso aqui. Essa opção não está na lista de recursos rejeitados do coreutils , mas o patch abaixo agora é rejeitado por Pádraig Brady após enviá-lo para a lista de discussão do coreutils. Pela resposta, fica claro que essa é uma razão filosófica (a lsprodução é para consumo humano).

Se você quiser experimentar se essa opção é razoável, faça o seguinte:

git clone git://git.sv.gnu.org/coreutils
cd coreutils
./bootstrap
./configure
make

em seguida, aplique o seguinte patch ao commit b938b6e289ef78815935ffa705673a6a8b2ee98e dd 2014-01-29:

From 6413d5e2a488ecadb8b988c802fe0a5e5cb7d8f4 Mon Sep 17 00:00:00 2001
From: Anthon van der Neut <address@hidden>
Date: Mon, 3 Feb 2014 15:33:50 +0100
Subject: [PATCH] ls: adding --zero/-z option, including tests

* src/ls.c has the necessary changes to allow -z/--zero option to be
  specified, resulting in a NUL seperated list of files. This
  allows the output of e.g. "ls -rtz" to be piped into other programs

* tests/ls/no-args.sh was extended to test the -z option

* test/ls/rt-zero.sh was added to test both the long and short option
  together with "-t"

This patch was inspired by numerous questions on unix.stackexchange.com
where the output of ls was piped into some other program, invariably
resulting in someone pointing out that is an unsafe practise because of
possible newlines and other characters in the filenames.
---
 src/ls.c            |   31 +++++++++++++++++++++++++------
 tests/ls/no-arg.sh  |    7 ++++++-
 tests/ls/rt-zero.sh |   38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 69 insertions(+), 7 deletions(-)
 create mode 100755 tests/ls/rt-zero.sh

diff --git a/src/ls.c b/src/ls.c
index 5d87dd3..962e6bb 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -381,6 +381,7 @@ static int file_size_width;
    many_per_line for just names, many per line, sorted vertically.
    horizontal for just names, many per line, sorted horizontally.
    with_commas for just names, many per line, separated by commas.
+   with_zero for just names, one per line, separated by NUL.

-l (and other options that imply -l), -1, -C, -x and -m control

    this parameter.  */
@@ -391,7 +392,8 @@ enum format
     one_per_line,              /* -1 */
     many_per_line,             /* -C */
     horizontal,                        /* -x */
-    with_commas                        /* -m */
+    with_commas,               /* -m */
+    with_zero,                 /* -z */
   };

static enum format format;

@@ -842,6 +844,7 @@ static struct option const long_options[] =
   {"block-size", required_argument, NULL, BLOCK_SIZE_OPTION},
   {"context", no_argument, 0, 'Z'},
   {"author", no_argument, NULL, AUTHOR_OPTION},
+  {"zero", no_argument, NULL, 'z'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -850,12 +853,12 @@ static struct option const long_options[] =
 static char const *const format_args[] =
 {
   "verbose", "long", "commas", "horizontal", "across",
-  "vertical", "single-column", NULL
+  "vertical", "single-column", "zero", NULL
 };
 static enum format const format_types[] =
 {
   long_format, long_format, with_commas, horizontal, horizontal,
-  many_per_line, one_per_line
+  many_per_line, one_per_line, with_zero
 };
 ARGMATCH_VERIFY (format_args, format_types);

@@ -1645,7 +1648,7 @@ decode_switches (int argc, char **argv)

     {
       int oi = -1;
       int c = getopt_long (argc, argv,
-                           "abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UXZ1",
+                           "abcdfghiklmnopqrstuvw:xzABCDFGHI:LNQRST:UXZ1",
                            long_options, &oi);
       if (c == -1)
         break;
@@ -1852,6 +1855,10 @@ decode_switches (int argc, char **argv)
             format = one_per_line;
           break;

+ case 'z':

+          format = with_zero;
+          break;
+
         case AUTHOR_OPTION:
           print_author = true;
           break;
@@ -2607,7 +2614,8 @@ print_dir (char const *name, char const *realname, bool 
command_line_arg)
                  ls uses constant memory while processing the entries of
                  this directory.  Useful when there are many (millions)
                  of entries in a directory.  */
-              if (format == one_per_line && sort_type == sort_none
+              if ((format == one_per_line || format == with_zero)
+                      && sort_type == sort_none
                       && !print_block_size && !recursive)
                 {
                   /* We must call sort_files in spite of
@@ -3598,6 +3606,14 @@ print_current_files (void)
         }
       break;

+ case with_zero:

+      for (i = 0; i < cwd_n_used; i++)
+        {
+          print_file_name_and_frills (sorted_file[i], 0);
+          putchar ('\0');
+        }
+      break;
+
     case many_per_line:
       print_many_per_line ();
       break;
@@ -4490,6 +4506,7 @@ print_many_per_line (void)
           indent (pos + name_length, pos + max_name_length);
           pos += max_name_length;
         }
+      putchar ('X'); // AvdN
       putchar ('\n');
     }
 }
@@ -4780,7 +4797,8 @@ Sort entries alphabetically if none of -cftuvSUX nor 
--sort is specified.\n\
   -F, --classify             append indicator (one of */=>@|) to entries\n\
       --file-type            likewise, except do not append '*'\n\
       --format=WORD          across -x, commas -m, horizontal -x, long -l,\n\
-                               single-column -1, verbose -l, vertical -C\n\
+                               single-column -1, verbose -l, vertical -C,\n\
+                               zeros -z\n\
       --full-time            like -l --time-style=full-iso\n\
 "), stdout);
       fputs (_("\
@@ -4888,6 +4906,7 @@ Sort entries alphabetically if none of -cftuvSUX nor 
--sort is specified.\n\
   -X                         sort alphabetically by entry extension\n\
   -Z, --context              print any security context of each file\n\
   -1                         list one file per line\n\
+  -z, --zero                 list files separated with NUL\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
diff --git a/tests/ls/no-arg.sh b/tests/ls/no-arg.sh
index e356a29..da28b96 100755
--- a/tests/ls/no-arg.sh
+++ b/tests/ls/no-arg.sh
@@ -30,11 +30,16 @@ out
 symlink
 EOF

-

 ls -1 > out || fail=1

compare exp out || fail=1 +/bin/echo -en "dir\00exp\00out\00symlink\00" > exp || framework_failure_

+
+ls --zero > out || fail=1
+
+compare exp out || fail=1
+
 cat > exp <<\EOF
 .:
 dir
diff --git a/tests/ls/rt-zero.sh b/tests/ls/rt-zero.sh
new file mode 100755
index 0000000..cdbd311
--- /dev/null
+++ b/tests/ls/rt-zero.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Make sure name is used as secondary key when sorting on mtime or ctime.
+
+# Copyright (C) 1998-2014 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ ls touch
+
+date=1998-01-15
+
+touch -d "$date" c || framework_failure_
+touch -d "$date" a || framework_failure_
+touch -d "$date" b || framework_failure_
+
+
+ls -zt a b c > out || fail=1
+/bin/echo -en "a\00b\00c\00" > exp
+compare exp out || fail=1
+
+rm -rf out exp
+ls -rt --zero a b c > out || fail=1
+/bin/echo -en "c\00b\00a\00" > exp
+compare exp out || fail=1
+
+Exit $fail
--
1.7.9.5

Depois de outro make, você pode testá-lo com:

  src/ls -rtz | xargs -0 -n1 src/ls -ld

Portanto, o patch funciona e não vejo uma razão para isso, mas isso não é prova de que não há razão técnica para deixar de fora a opção. ls -R0pode não fazer muito sentido, mas também o ls -Rmque lspode ser feito imediatamente.

Anthon
fonte
Ter -ze --zeroé mais em linha com a espécie (também em coreutils.
Anthon