GNU/Linux >> Belajar Linux >  >> Linux

Looping Melalui File Dengan Spasi Dalam Nama??

Pertanyaan ini sudah memiliki jawaban di sini :Mengapa mengulang hasil find merupakan praktik yang buruk?

(8 jawaban)
Tutup 3 tahun yang lalu.

Saya menulis skrip berikut untuk membedakan output dari dua direktori dengan semua file yang sama di dalamnya:

#!/bin/bash

for file in `find . -name "*.csv"`  
do
     echo "file = $file";
     diff $file /some/other/path/$file;
     read char;
done

Saya tahu ada cara lain untuk mencapai ini. Anehnya, skrip ini gagal ketika file memiliki spasi di dalamnya. Bagaimana saya bisa mengatasi ini?

Contoh keluaran find:

./zQuery - abc - Do Not Prompt for Date.csv

Jawaban yang Diterima:

Jawaban singkat (paling dekat dengan jawaban Anda, tetapi menggunakan spasi)

OIFS="$IFS"
IFS=$'n'
for file in `find . -type f -name "*.csv"`  
do
     echo "file = $file"
     diff "$file" "/some/other/path/$file"
     read line
done
IFS="$OIFS"

Jawaban yang lebih baik (juga menangani karakter pengganti dan baris baru dalam nama file)

find . -type f -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

Jawaban terbaik (berdasarkan jawaban Gilles)

find . -type f -name '*.csv' -exec sh -c '
  file="$0"
  echo "$file"
  diff "$file" "/some/other/path/$file"
  read line </dev/tty
' exec-sh {} ';'

Atau bahkan lebih baik, untuk menghindari menjalankan satu sh per berkas:

find . -type f -name '*.csv' -exec sh -c '
  for file do
    echo "$file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
  done
' exec-sh {} +

Jawaban panjang

Anda memiliki tiga masalah:

  1. Secara default, shell membagi output perintah pada spasi, tab, dan baris baru
  2. Nama file dapat berisi karakter wildcard yang akan diperluas
  3. Bagaimana jika ada direktori yang namanya berakhiran *.csv ?

1. Memisahkan hanya pada baris baru

Untuk mengetahui apa yang harus disetel file untuk, shell harus mengambil output dari find dan menafsirkannya entah bagaimana, jika tidak file hanya akan menjadi seluruh output dari find .

Shell membaca IFS variabel, yang disetel ke <space><tab><newline> secara default.

Kemudian terlihat pada setiap karakter pada output find . Segera setelah melihat karakter apa pun yang ada di IFS , ia berpikir bahwa menandai akhir dari nama file, sehingga menetapkan file ke karakter apa pun yang dilihatnya sampai sekarang dan menjalankan loop. Kemudian dimulai dari tempat terakhirnya untuk mendapatkan nama file berikutnya, dan menjalankan loop berikutnya, dll., hingga mencapai akhir keluaran.

Jadi ini efektif melakukan ini:

for file in "zquery" "-" "abc" ...

Untuk memberitahunya agar hanya membagi input pada baris baru, Anda perlu melakukan

IFS=$'n'

sebelum for ... find perintah.

Itu menetapkan IFS ke satu baris baru, sehingga hanya terbagi pada baris baru, bukan spasi dan tab juga.

Jika Anda menggunakan sh atau dash bukannya ksh93 , bash atau zsh , Anda perlu menulis IFS=$'n' seperti ini sebagai gantinya:

IFS='
'

Itu mungkin cukup untuk membuat skrip Anda berfungsi, tetapi jika Anda tertarik untuk menangani beberapa kasus sudut lainnya dengan benar, baca terus…

2. Memperluas $file tanpa karakter pengganti

Di dalam loop tempat Anda melakukannya

diff $file /some/other/path/$file

shell mencoba untuk memperluas $file (lagi!).

Itu bisa berisi spasi, tapi karena kita sudah mengatur IFS di atas, itu tidak akan menjadi masalah di sini.

Tapi itu juga bisa berisi karakter wildcard seperti * atau ? , yang akan mengarah pada perilaku yang tidak terduga. (Terima kasih kepada Gilles karena telah menunjukkan hal ini.)

Untuk memberi tahu shell agar tidak memperluas karakter wildcard, letakkan variabel di dalam tanda kutip ganda, mis.

diff "$file" "/some/other/path/$file"

Masalah yang sama juga bisa menggigit kita di

for file in `find . -name "*.csv"`

Misalnya, jika Anda memiliki tiga file ini

file1.csv
file2.csv
*.csv

(sangat tidak mungkin, tapi masih mungkin)

Terkait:Jika saya mengubah izin pada file tar, apakah itu berlaku untuk file di dalamnya?

Seolah-olah Anda telah berlari

for file in file1.csv file2.csv *.csv

yang akan diperluas ke

for file in file1.csv file2.csv *.csv file1.csv file2.csv

menyebabkan file1.csv dan file2.csv untuk diproses dua kali.

Sebaliknya, kita harus melakukan

find . -name "*.csv" -print | while IFS= read -r file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

read membaca baris dari input standar, membagi baris menjadi kata-kata sesuai dengan IFS dan menyimpannya dalam nama variabel yang Anda tentukan.

Di sini, kami memintanya untuk tidak membagi baris menjadi kata-kata, dan menyimpan baris di $file .

Perhatikan juga bahwa read line telah berubah menjadi read line </dev/tty .

Ini karena di dalam loop, input standar berasal dari find melalui jalur pipa.

Jika kita baru saja read , itu akan memakan sebagian atau seluruh nama file, dan beberapa file akan dilewati.

/dev/tty adalah terminal tempat pengguna menjalankan skrip. Perhatikan bahwa ini akan menyebabkan kesalahan jika skrip dijalankan melalui cron, tetapi saya menganggap ini tidak penting dalam kasus ini.

Lalu, bagaimana jika nama file berisi baris baru?

Kita bisa mengatasinya dengan mengubah -print ke -print0 dan menggunakan read -d '' di ujung saluran:

find . -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read char </dev/tty
done

Ini membuat find letakkan byte nol di akhir setiap nama file. Byte nol adalah satu-satunya karakter yang tidak diperbolehkan dalam nama file, jadi ini harus menangani semua kemungkinan nama file, tidak peduli seberapa anehnya.

Untuk mendapatkan nama file di sisi lain, kami menggunakan IFS= read -r -d '' .

Di mana kami menggunakan read di atas, kami menggunakan pembatas baris default dari baris baru, tetapi sekarang, find menggunakan null sebagai pembatas baris. Di bash , Anda tidak dapat meneruskan karakter NUL dalam argumen ke perintah (bahkan yang bawaan), tetapi bash mengerti -d '' sebagai artinya dibatasi NUL . Jadi kami menggunakan -d '' untuk membuat read gunakan pembatas baris yang sama dengan find . Perhatikan bahwa -d $'

Linux
  1. Mengulang konten file di Bash

  2. Ulangi daftar file dengan spasi

  3. Menggunakan garis bawah dalam nama file?

  1. Salin file di terminal Linux

  2. Bagaimana cara mengganti nama file dengan spasi menggunakan shell Linux?

  3. AWK dan nama file dengan spasi di dalamnya.

  1. Temukan file dan direktori di Linux dengan perintah find

  2. Pindahkan file di terminal Linux

  3. Bagaimana Cara Mengedit File Sistem Dengan Kate Editor??