Ratakan Direktori tetapi Pertahankan Nama Direktori di Nama File Baru

11

Bagaimana saya bisa meratakan direktori dalam format berikut?

Sebelum: ./aaa/bbb/ccc.png

Setelah: ./aaa-bbb-ccc.png

Nathan JB
sumber
NB: Harap pertanggungjawabkan nama file dengan karakter aneh (mis. Spasi)
Nathan JB
3
Anda juga harus menentukan cara menangani kasus yang mungkin ada ./foo/bar/baz.pngdan ./foo-bar-baz.pngkeduanya ada. Saya berasumsi Anda tidak ingin mengganti yang terakhir dengan yang pertama?
jw013
Dalam satu kasus saya, itu tidak relevan, tetapi jawabannya memang harus menjelaskan sehingga orang lain dapat mengandalkannya! Terima kasih!
Nathan JB

Jawaban:

7

Peringatan: Saya mengetik sebagian besar perintah ini langsung di browser saya. Lektor peringatan.

Dengan zsh dan zmv :

zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'

Penjelasan: Pola ini **/*cocok dengan semua file di subdirektori dari direktori saat ini, secara rekursif (tidak cocok dengan file di direktori saat ini, tetapi ini tidak perlu diganti namanya). Dua pasang kurung pertama adalah grup yang dapat disebut sebagai $1dan $2dalam teks pengganti. Pasangan terakhir kurung menambahkan D kualifikasi glob sehingga file dot tidak dihilangkan. -o -iartinya meneruskan -iopsi ke mvsehingga Anda diminta jika file yang ada akan ditimpa.


Dengan hanya alat POSIX:

find . -depth -exec sh -c '
    for source; do
      case $source in ./*/*)
        target="$(printf %sz "${source#./}" | tr / -)";
        mv -i -- "$source" "${target%z}";;
      esac
    done
' _ {} +

Penjelasan: casepernyataan menghilangkan direktori saat ini dan subdirektori tingkat atas dari direktori saat ini. targetberisi nama file sumber ( $0) dengan ./strip utama dan semua garis miring diganti dengan tanda hubung, ditambah final z. Final zada di sana dalam kasus nama file berakhir dengan baris baru: jika tidak, substitusi perintah akan menghapusnya.

Jika Anda findtidak mendukung -exec … +(OpenBSD, saya melihat Anda):

find . -depth -exec sh -c '
    case $0 in ./*/*)
      target="$(printf %sz "${0#./}" | tr / -)";
      mv -i -- "$0" "${target%z}";;
    esac
' {} \;

Dengan bash (atau ksh93), Anda tidak perlu memanggil perintah eksternal untuk mengganti garis miring dengan tanda hubung, Anda dapat menggunakan ekspansi parameter ksh93 dengan konstruksi penggantian string ${VAR//STRING/REPLACEMENT}:

find . -depth -exec bash -c '
    for source; do
      case $source in ./*/*)
        source=${source#./}
        target="${source//\//-}";
        mv -i -- "$source" "$target";;
      esac
    done
' _ {} +
Gilles 'SANGAT berhenti menjadi jahat'
sumber
for sourceContoh - contoh melewatkan file / dir pertama yang disajikan ... Saya akhirnya menemukan mengapa Anda (biasanya) telah menggunakan _sebagai $0:) ... dan terima kasih atas jawaban yang bagus. Saya belajar banyak dari mereka.
Peter.O
Perhatikan bahwa contoh zmv hanya menjalankan proses kering: ia mencetak perintah langkah tetapi tidak menjalankannya. Untuk benar-benar menjalankan perintah-perintah itu, hapus n di -Qn.
Peter
5

Meskipun sudah ada jawaban bagus di sini, saya menemukan ini lebih intuitif, di dalam bash:

find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;

Di mana aaadirektori Anda saat ini. File-file yang dikandungnya akan dipindahkan ke direktori saat ini (hanya menyisakan direktori kosong di aaa). Kedua panggilan untuk trmenangani direktori dan ruang (tanpa logika untuk tebas lolos - latihan untuk pembaca).

Saya menganggap ini lebih intuitif dan relatif mudah untuk di-tweak. Yaitu saya bisa mengubah findparameter jika mengatakan saya hanya ingin mencari file .png. Saya dapat mengubah trpanggilan, atau menambahkan lebih banyak sesuai kebutuhan. Dan saya dapat menambahkan echosebelumnya mvjika saya ingin memeriksa secara visual bahwa itu akan melakukan hal yang benar (kemudian ulangi tanpa tambahan echohanya jika semuanya terlihat benar:

find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;

Perhatikan juga saya menggunakan find aaa... dan tidak find .... atau find aaa/... karena dua yang terakhir akan meninggalkan artefak lucu di nama file terakhir.

Dave Cohen
sumber
Terima kasih untuk liner yang satu ini - ini bekerja dengan baik untuk saya ketika saya melakukannya dari luar direktori (yang persis seperti yang Anda katakan). Ketika saya mencoba melakukan dari dalam direktori (berharap untuk menghindari nama direktori root yang ditambahkan) semua file "menghilang". Saya telah mengubahnya menjadi file tersembunyi di direktori yang sama.
dgig
2
find . -mindepth 2 -type f -name '*' |
  perl -l000ne 'print $_;  s/\//-/g; s/^\.-/.\// and print' |
    xargs -0n2 mv 

Catatan: ini tidak akan berfungsi untuk nama file yang mengandung \n.
Ini, tentu saja hanya memindahkan ffile jenis ...
Satu-satunya bentrokan nama akan dari file yang sudah ada di pwd

Diuji dengan bagian dasar ini

rm -fr junk
rm -f  junk*hello*

mkdir -p  junk/junkier/junkiest
touch    'hello    hello'
touch    'junk/hello    hello'
touch    'junk/junkier/hello    hello'
touch    'junk/junkier/junkiest/hello    hello'

Yang menghasilkan

./hello    hello
./junk-hello    hello
./junk-junkier-hello    hello
./junk-junkier-junkiest-hello    hello
Peter.O
sumber
0

Ini adalah lsversi .. selamat datang komentar. Ini berfungsi untuk nama file yang aneh seperti ini "j1ळ\n\001\n/j2/j3/j4/\nh  h\n", tetapi saya benar-benar tertarik untuk mengetahui adanya jebakan. Ini lebih merupakan latihan dalam "dapatkah difitnah lsbenar-benar melakukannya (dengan kuat)?"

ls -AbQR1p * |                        # paths in "quoted" \escaped format
    sed -n '/":$/,${/.[^/]$/p}' |     # skip files in pwd, blank and dir/ lines
    { bwd="$PWD"; while read -n1;     # save base dir strip leading "quote
                        read -r p; do # read path
        printf -vs "${p%\"*}"         # strip trailing quote"(:)
        [[ $p == *: ]] && {           # this is a directory
            cd   "$bwd/$s"            # change into new directory
            rnp="${s//\//-}"-         # relative name prefix
        } || mv "$s" "$bwd/$rnp$s"
      done; }
Peter.O
sumber
-1

Cukup pipa nama file + path ke trperintah.tr <replace> <with>

for FILE in `find aaa -name "*.png"`
do
  NEWFILE=`echo $FILE | tr '/' '-'`
  cp -v $FILE $NEWFILE
done
emcconville
sumber
1
Ini tidak menangani nama file aneh dengan aman. Ruang akan memecahnya (antara lain).
jw013
Pada pandangan pertama, ini adalah solusi yang bersih dan mudah dibaca tetapi @ jw013 benar, itu tidak akan menangani spasi dengan baik. Apakah mengubah cpsaluran akan cp -v "$FILE" "$NEWFILE"membantu? (Juga, Anda seharusnya tidak hanya mengasumsikan file png.)
Nathan JB
2
@ NathanJ.Brauer Menambahkan kutipan ke cpbaris tidak cukup. Seluruh pendekatan findoutput parsing dengan forloop cacat. Satu-satunya cara aman untuk digunakan findadalah dengan -exec, atau -print0jika tersedia (kurang portabel dari -exec). Saya akan menyarankan alternatif yang lebih kuat tetapi pertanyaan Anda saat ini tidak ditentukan.
jw013
-2

Aman untuk spasi dalam nama file:

#!/bin/bash

/bin/find $PWD -type f | while read FILE
do
    _new="${FILE//\//-}"
    _new="${_new:1}"
    #echo $FILE
    #echo $_new

    /bin/mv "$FILE" "$_new"
done

Berlari dengan saya cpalih - alih mvkarena alasan yang jelas, tetapi harus menghasilkan hal yang sama.

Tim
sumber
3
type find-> find is /usr/bin/finddi mesin saya. Menggunakan PATHsebagai nama variabel dalam sebuah skrip adalah ide yang buruk. Juga, skrip Anda manglesi nama file dengan spasi atau garis miring terbalik, karena Anda menyalahgunakan readperintah (cari situs ini IFS read -r).
Gilles 'SANGAT berhenti menjadi jahat'
find is hashed (/bin/find), relevan bagaimana? Distro yang berbeda berbeda?
Tim
Tahu aku seharusnya menjauh dari ini, umpan burung pemakan bangkai.
Tim
@Tim Anda selalu dapat menghapus jawaban untuk mendapatkan kembali perwakilan Anda (dan juga jawaban saya b / c biaya rep untuk downvote). Jalur hard-coding ke dalam skrip tampaknya menunjukkan bahwa Anda kehilangan titik PATHvariabel lingkungan, yang merupakan sesuatu yang digunakan oleh setiap sistem Unix. Jika Anda menulis /bin/findmaka skrip Anda benar-benar rusak pada setiap sistem (Gilles, milikku, dan mungkin OP) yang tidak memiliki /bin/find(misalnya b / c itu ada di /usr/bin/find). Inti dari PATHvariabel lingkungan adalah menjadikan ini bukan masalah.
jw013
By the way, $(pwd)sudah tersedia dalam variabel: $PWD. Tetapi Anda tidak boleh menggunakan jalur absolut di sini: jika skrip Anda dipanggil dari, katakanlah /home/tim, ia mencoba mengubah namanya /home/tim/some/pathmenjadi /home-tim-some-path.
Gilles 'SANGAT berhenti menjadi jahat'