Saya secara konsisten melihat jawaban yang mengutip tautan ini yang menyatakan dengan pasti, "Jangan parsing ls
!" Ini menggangguku karena beberapa alasan:
Tampaknya informasi dalam tautan tersebut telah diterima secara grosir dengan sedikit pertanyaan, meskipun saya dapat memilih setidaknya beberapa kesalahan dalam membaca santai.
Tampaknya juga masalah yang dinyatakan dalam tautan tersebut tidak memicu keinginan untuk menemukan solusi.
Dari paragraf pertama:
... ketika Anda meminta
[ls]
daftar file, ada masalah besar: Unix memungkinkan hampir semua karakter dalam nama file, termasuk spasi, baris baru, koma, simbol pipa, dan hampir semua hal lain yang pernah Anda coba gunakan sebagai pembatas kecuali NUL. ...ls
memisahkan nama file dengan baris baru. Ini bagus sampai Anda memiliki file dengan baris baru dalam namanya. Dan karena saya tidak tahu implementasi apa punls
yang memungkinkan Anda untuk mengakhiri nama file dengan karakter NUL alih-alih baris baru, ini membuat kami tidak dapat memperoleh daftar nama file dengan amanls
.
Nyebelin, kan? Bagaimana pernah kita dapat menangani baris baru dihentikan dataset terdaftar untuk data yang mungkin berisi baris baru? Nah, jika orang-orang yang menjawab pertanyaan di situs web ini tidak melakukan hal semacam ini setiap hari, saya mungkin berpikir kami berada dalam masalah.
Kenyataannya adalah, sebagian besar ls
implementasi sebenarnya menyediakan api yang sangat sederhana untuk mem-parsing output mereka dan kita semua sudah melakukannya tanpa menyadarinya. Anda tidak hanya dapat mengakhiri nama file dengan null, Anda juga dapat memulai dengan nama null atau dengan string arbitrer lainnya yang mungkin Anda inginkan. Terlebih lagi, Anda dapat menetapkan string sewenang-wenang ini per jenis file . Tolong pertimbangkan:
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^@^@^@$
^@
Lihat ini untuk lebih lanjut.
Sekarang bagian selanjutnya dari artikel ini yang benar-benar membuat saya:
$ 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
Masalahnya adalah bahwa dari output
ls
, Anda atau komputer tidak dapat menentukan bagian mana yang merupakan nama file. Apakah itu setiap kata? Tidak. Apakah itu setiap baris? Tidak. Tidak ada jawaban yang benar untuk pertanyaan ini selain: Anda tidak tahu.Perhatikan juga bagaimana
ls
kadang-kadang data data file Anda rusak (dalam kasus kami, itu mengubah\n
karakter di antara kata "a" dan "baris baru" menjadi tanda tanya? ......
Jika Anda hanya ingin mengulang semua file dalam direktori saat ini, gunakan
for
loop dan glob:
for f in *; do
[[ -e $f ]] || continue
...
done
Penulis menyebutnya mengacaukan nama file ketika ls
mengembalikan daftar nama file yang mengandung shell shell dan kemudian merekomendasikan menggunakan shell glob untuk mengambil daftar file!
Pertimbangkan yang berikut ini:
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 mendefinisikan yang -1
dan -q
ls
operan sehingga:
-q
- Paksa setiap instance karakter nama file yang tidak dapat dicetak dan<tab>
s ditulis sebagai karakter tanda tanya ('?'
). Implementasi dapat menyediakan opsi ini secara default jika outputnya ke perangkat terminal.
-1
- (Digit angka satu.) Memaksa output menjadi satu entri per baris.
Globbing bukan tanpa masalah sendiri - ?
cocok dengan karakter apa pun sehingga beberapa ?
hasil pencocokan dalam daftar akan cocok dengan file yang sama beberapa kali. Itu mudah ditangani.
Meskipun bagaimana melakukan hal ini bukan itu intinya - tidak banyak yang harus dilakukan dan ditunjukkan di bawah ini - saya tertarik mengapa tidak . Saat saya mempertimbangkannya, jawaban terbaik untuk pertanyaan itu telah diterima. Saya sarankan Anda mencoba untuk lebih sering fokus memberi tahu orang lain apa yang bisa mereka lakukan daripada apa yang tidak bisa mereka lakukan . Sepertinya Anda jauh lebih kecil kemungkinannya untuk terbukti salah.
Tetapi mengapa bahkan mencoba? Memang, motivasi utama saya adalah bahwa orang lain terus mengatakan kepada saya bahwa saya tidak bisa. Saya tahu betul bahwa ls
keluarannya teratur dan dapat diprediksi seperti yang Anda harapkan selama Anda tahu apa yang harus dicari. Informasi yang salah menggangguku lebih daripada melakukan banyak hal.
Yang benar adalah, meskipun, dengan perkecualian terkemuka untuk jawaban Patrick dan Wumpus Q. Wumbley (terlepas dari pegangan luar biasa yang terakhir) , saya menganggap sebagian besar informasi dalam jawaban di sini sebagian besar benar - sebuah bola shell lebih mudah digunakan. dan umumnya lebih efektif ketika mencari direktori saat ini daripada parsing ls
. Namun, mereka tidak, setidaknya menurut saya, cukup alasan untuk membenarkan menyebarkan informasi salah yang dikutip dalam artikel di atas dan mereka juga tidak bisa dibenarkan untuk " tidak pernah parse ls
. "
Harap dicatat bahwa hasil yang tidak konsisten Patrick jawaban ini sebagian besar hasil dari dia menggunakan zsh
itu bash
. zsh
- secara default - tidak $(
perintah -kata menggantikan )
hasil dengan cara yang portabel. Jadi ketika dia bertanya kemana sisa file pergi? jawaban atas pertanyaan itu adalah kulitmu memakannya. Inilah sebabnya mengapa Anda perlu mengatur SH_WORD_SPLIT
variabel saat menggunakan zsh
dan menangani kode shell portabel. Saya menganggap kegagalannya untuk mencatat ini dalam jawabannya sangat menyesatkan.
Jawaban Wumpus tidak cocok untuk saya - dalam konteks daftar ?
karakternya adalah sebuah shell glob. Saya tidak tahu bagaimana lagi mengatakan itu.
Untuk menangani kasus multi hasil, Anda harus membatasi kerakusan glob. Berikut ini hanya akan membuat basis uji nama file yang mengerikan dan menampilkannya untuk Anda:
{ 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" $# )
}
KELUARAN
`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b
NOW LITERAL - COMMA,SEP
?
\, ?
^, ?
`, ?
b, [ \, [
\, ] ^, ]
^, _ `, _
`, a b, a
b
FILE COUNT: 12
Sekarang aku akan aman setiap karakter yang bukan /slash
, -dash
, :colon
, atau alpha-numerik karakter dalam segumpal shell kemudian sort -u
daftar untuk hasil yang unik. Ini aman karena ls
telah menyelamatkan karakter yang tidak dapat dicetak untuk kita. Menonton:
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
KELUARAN:
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'
Di bawah ini saya mendekati masalah lagi tetapi saya menggunakan metodologi yang berbeda. Ingat bahwa - selain \0
nol - /
karakter ASCII adalah satu-satunya byte yang dilarang dalam pathname. Saya kesampingkan gumpalan di sini dan sebagai gantinya menggabungkan opsi POSIX yang ditentukan -d
untuk ls
dan juga -exec $cmd {} +
konstruksi POSIX yang ditentukan untuk find
. Karena find
hanya akan secara alami memancarkan satu /
secara berurutan, yang berikut ini dengan mudah mendapatkan daftar file yang rekursif dan dibatasi dengan andal termasuk semua informasi gigi untuk setiap entri. Bayangkan saja apa yang mungkin Anda lakukan dengan sesuatu seperti ini:
#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
bisa sangat berguna - terutama ketika keunikan hasil dipertanyakan.
ls -1iq |
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' |
tr -d '\n' |
xargs find
Ini hanyalah cara yang paling portabel yang dapat saya pikirkan. Dengan GNU ls
yang bisa Anda lakukan:
ls --quoting-style=WORD
Dan yang terakhir, inilah metode parsingls
yang lebih sederhana yang sering saya gunakan ketika membutuhkan nomor inode:
ls -1iq | grep -o '^ *[0-9]*'
Itu hanya mengembalikan nomor inode - yang merupakan opsi POSIX berguna lainnya.
stat
jawaban saya, karena sebenarnya memeriksa bahwa setiap file ada. Bit Anda di bagian bawah dengan sed
hal itu tidak berfungsi.
ls
pada awalnya? Apa yang Anda gambarkan sangat sulit. Saya harus mendekonstruksi untuk memahami semua itu dan saya pengguna yang relatif kompeten. Anda tidak mungkin mengharapkan rata-rata Joe Anda untuk dapat menangani sesuatu seperti ini.
ls
output salah tercakup dengan baik di tautan asli (dan di banyak tempat lain). Pertanyaan ini akan masuk akal jika OP meminta bantuan untuk memahaminya, tetapi sebaliknya OP hanya mencoba membuktikan bahwa penggunaannya yang salah itu tidak masalah.
parsing ls is bad
. Melakukan for something in $(command)
dan mengandalkan pemisahan kata untuk mendapatkan hasil yang akurat adalah buruk bagi sebagian besar command's
yang tidak memiliki output sederhana.
time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'
= 3.18s vstime bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'
= 1.28s