temukan | xargs shasum membuat checksum dari file checksum itu sendiri (prematur) dan gagal saat memeriksa

10

Masalah saya (dalam skrip dengan #!/bin/sh) adalah sebagai berikut: Saya mencoba untuk checksum semua file dalam direktori untuk keperluan arsip. File checksum (dalam kasus saya sh1) dengan semua nama file harus berada di direktori yang sama. Katakanlah kita memiliki direktori ~/testdengan file f1dan f2:.

mkdir ~/test
cd ~/test
echo "hello" > f1
echo "world" > f2

Sekarang menghitung checksum dengan

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum

tidak persis apa yang saya inginkan, itu daftar semua file dari direktori saat ini saja dan menghitung jumlah sha1 (maxdepth dapat diubah kemudian). Output pada STDOUT adalah:

f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2

Sayangnya, ketika mencoba menyimpan ini ke file dengan

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum > sums.sha1

file yang dihasilkan menampilkan checksum untuk dirinya sendiri:

da39a3ee5e6b4b0d3255bfef95601890afd80709  sums.sha1
f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2  

dan karena itu gagal di kemudian hari shasum --check, karena masalah nyata modifikasi file tambahan saat menyimpan jumlah terakhir.

Saya melihat sekeliling dan dengan menggunakan -pflag untuk xargs, saya menemukan bahwa entah bagaimana ia membuat file output bahkan sebelum mengeksekusi perintah find, oleh karena itu file tambahan ditemukan dan akan checksummed ...

Saya tahu bahwa sebagai solusi saya dapat menyimpan checksum ke lokasi lain (direktori temp via mktemp) atau mengecualikannya di find khusus, tetapi saya ingin memahami mengapa berperilaku seperti itu - yang menurut saya tidak berguna, misalnya jika perintah pertama akan memeriksa apakah file output sudah ada di disk, itu tidak akan pernah mendapatkan jawaban yang benar ...

pengguna121391
sumber
8
Bukan xargs, itu adalah shell itu sendiri yang membuat file ini, karena sebelum perintah apa pun dijalankan terlebih dahulu shell mengarahkan ulang semua input, output dan pipa, sehingga ketika findmulai file output sudah ada. Gunakan -execsebagai gantinya:find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
jimmij
@jimmij, itu juga tidak dijamin berfungsi jika beberapa shdoa diperlukan. Perhatikan bahwa Anda perlu argumen $0sebelumnya {}.
Stéphane Chazelas
@ jimmij Jawaban Anda yang lain yang disarankan teetelah menghilang? Saya mencobanya dan berfungsi dengan baik, saya juga menekan STDOUT dengan menambahkan 1>/dev/null. Apakah ada yang salah dengan jawabannya atau itu bug?
user121391
@ user121391 Stephane menunjukkan bahwa terkadang ada masalah kondisi ras, apa yang tampaknya benar. Saya membatalkan penghapusan untuk sementara waktu sehingga Anda dapat melihat, tetapi jika Anda memiliki banyak file dalam daftar itu perintah bisa salah.
jimmij
@ ahmijij ah, begitu. Mungkin bermanfaat jika Anda awalan dengan peringatan tentang masalah, karena saya pikir tidak begitu diketahui bahwa ini bisa terjadi. Kalau tidak, saya akan menerima jawaban Anda untuk kasus-kasus jika berjalan berulang termasuk file lama dan Anthon untuk kasus-kasus di mana itu harus ditimpa.
user121391

Jawaban:

12

Anda dapat mencegah mencapai file xargsmenggunakan:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\n' |
  xargs -r shasum -- > sums.sha1

Untuk mencegah masalah dengan nama file yang memiliki baris kosong atau baris baru atau kutipan atau garis miring terbalik, saya akan menggunakan:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\0' |
  xargs -r0 shasum -- > sums.sha1

sebagai gantinya.

The --adalah untuk menghindari masalah dengan nama file yang dimulai dengan -. Namun itu tidak akan membantu untuk file yang dipanggil -. Seandainya Anda menggunakan -print0alih-alih -printf '%P\0', Anda tidak akan membutuhkan --dan tidak akan memiliki masalah dengan -file tersebut.

Anthon
sumber
Solusi Anda adalah apa yang akhirnya saya gunakan. Saya terutama suka bahwa menjalankan selanjutnya tidak mengulangi file checksum dan inflat direktori. Juga, dalam skrip saya, saya biasa basenamemendapatkan nama file sums.sha1 dari path lengkap yang diberikan (ini tidak termasuk dalam pertanyaan, tetapi mungkin membantu orang lain).
user121391
7

Karena Anda menggunakan -maxdepth 1, saya menganggap Anda tidak ingin rekursi. Jika demikian, lakukan saja di shell sebagai gantinya:

for f in ~/test/*; do
    shasum -- "$f"
done > sums.sha1

Untuk melewati direktori, Anda dapat melakukan:

for f in ~/test/*; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Jika Anda memang membutuhkan rekursi dan menggunakan bash, lakukan:

shopt -s globstar
for f in ~/test/**; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Perhatikan bahwa semua pendekatan ini bermanfaat untuk mengerjakan nama file yang sewenang-wenang, termasuk yang memiliki spasi, baris baru atau apa pun.

terdon
sumber
Saya pikir Anda akan menyebutkan bahwa ini memecahkan masalah OP akan memiliki nama file dengan baris baru di dalamnya juga. Di sisi lain jika sums.sha1sudah ada (dari lari sebelumnya) solusi Anda akan memasukkannya.
Anthon
Maaf, saya tidak mengklarifikasi sebelumnya: maxdepth hanya digunakan dalam contoh ini, saya menggunakan fungsi di mana pengguna / skrip dapat memberikan nilai apa pun, meskipun saat ini saya hanya perlu kedalaman 1.
user121391
@ user121391 lihat jawaban yang diperbarui untuk pendekatan rekursif.
terdon
Perhatikan bahwa ia juga akan mencoba untuk memeriksa jenis file non-reguler lainnya seperti pipa, perangkat ... (dan symlink ke sana).
Stéphane Chazelas
Terima kasih, secara pribadi saya menggunakan sh, tetapi jawaban Anda mungkin membantu orang lain.
user121391
4

dengan zsh:

shasum -- *(D.) > sums.sha1

Glob akan diperluas sebelum pengalihan dilakukan, sehingga sums.sha1tidak akan dimasukkan jika tidak ada di tempat pertama.

Dadalah memasukkan dot-file (file tersembunyi) seperti biasa find. .adalah memilih hanya file biasa (seperti milik Anda -type f).

Untuk mengecualikan sums.sha1tetap jika itu ada di tempat pertama:

setopt extendedglob # best in ~/.zshrc
shasum -- ^sums.sha1(D.) > sums.sha1

Perhatikan bahwa mereka menjalankan satu perintah shasum, sehingga Anda mungkin akhirnya melihat kesalahan "Daftar Arg terlalu panjang" jika daftar itu besar. Untuk mengatasinya:

autoload zargs
zargs -e/ -- *(D.) / shasum > sums.sha1

Saya akan merekomendasikan menggunakan ./*daripada *menghindari potensi masalah dengan file yang disebut -.

Stéphane Chazelas
sumber
Saya mengedit pertanyaan dengan jenis shell, tetapi jawaban Anda mengingatkan saya bahwa saya ingin beralih ke zsh beberapa waktu lalu ...;)
user121391
1

Sebagai jawaban lain sudah menyatakan masalahnya adalah bahwa shell membuka dan membuat sums.sha1file, sebelum menjalankan pipa Anda. Anda dapat menggunakan program spongeyang merupakan bagian dari moreutilspaket banyak distribusi. Berbeda dengan pengalihan shell spongeakan menunggu sampai semuanya diterima, sebelum membuka file. Biasanya digunakan ketika Anda ingin menulis file yang Anda baca di pipa yang sama.

Dalam kasus Anda digunakan seperti ini:

$ find -maxdepth 1 -type f -printf '%P\n' |xargs shasum |sponge sums.sha1
$ cat sums.sha1
31836aeaab22dc49555a97edb4c753881432e01d  B
7d157d7c000ae27db146575c08ce30df893d3a64  A
TimWolla
sumber
0

Sebagai alternatif dari find / xargs dll, Anda mungkin ingin sha1deep. Mungkin dalam paket yang berbeda - di kotak saya itu datang dalam paket md5deep.

Seperti orang lain mengatakan sums.sha1 dibuat oleh shell bahkan sebelum find dimulai. Trik dengan ! -name sums.sha1untuk findakan bekerja, juga akan

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum | grep -v ' sums\.sha1$' > sums.sha1
Torinthiel
sumber