Salin grup file (Nama file *) ke cadangan (Nama file * .bak)


13

Latar Belakang

Di Linux Anda dapat:

  • Daftar grup file dengan ls Filename*
  • Hapus sekelompok file dengan rm Filename*
  • Pindahkan sekelompok file dengan mv Filename* /New/Directory
  • Tetapi Anda tidak dapat menyalin sekelompok file dengan:cp Filename* *.bak

Ubah cpperintah Linux untuk menyalin grup file

Saya memiliki sekelompok file yang ingin saya salin tanpa memasukkan nama satu per satu dan menggunakan cpperintah:

$ ls gmail-meta3*
gmail-meta3                          gmail-meta3-REC-1558392194-26467821
gmail-meta3-LAB-1558392194-26467821  gmail-meta3-YAD-1558392194-26467821

Bagaimana saya bisa menggunakan sesuatu seperti perintah DOS lama copy gmail-meta3* *.bak?

Saya tidak ingin mengetik perintah serupa empat kali:

cp gmail-meta3-LAB-1558392194-26467821 gmail-meta3-LAB-1558392194-26467821.bak

Saya mencari skrip / fungsi / aplikasi yang menerima parameter untuk grup nama file lama dan baru dan bukan sesuatu dengan nama file hard-coded. Misalnya, pengguna dapat mengetik:

copy gmail-meta3* *.bak

atau mereka dapat mengetik:

copy gmail-meta3* save-*

1
Masalahnya bagi saya menggunakan operator glob dua kali yang tidak ada dari perintah Anda yang lain gunakan. bash tidak cukup pintar untuk menanganinya.
qwr

2
@ qwr, fakta bahwa bash memperluas metakarakter dan tokenizes input sebelum menyerahkannya ke perintah yang akan dieksekusi adalah bagian dari desain shell UNIX. Entah bagaimana mencoba dan memprogram pengecualian untuk perintah cp akan merusak seluruh konsistensi bash, yang tidak akan menjadi pintar sama sekali. Sebagai latihan, coba dan cari tahu apa yang terjadi di sini, dan mengapa ekspansi metacharacter shell yang membuatnya begitu:touch aa ab ba; mkdir bb; cp a* b*; ls *
Mike S

@ Mike Terima kasih untuk petunjuknya. Kemarin orang lain mengatakan Anda dapat menggunakan wildcard *untuk nama file sumber tetapi tidak untuk nama file target. Seperti wildcard pengganti (saya pikir ##disarankan tapi saya condong ke arah %) harus digunakan untuk target. Saya pikir ini yang Anda perkuat? Saya tidak berharap untuk mengubah cpperintah sama sekali. Cukup buat skrip wrapper yang disebut copyyang meniru (sesuai alasan) perintah salin DOS.
WinEunuuchs2Unix

@ WinEunuuchs2Unix orang itu benar. Metakarakter shell tidak tergantung pada perintah. Jadi semua wildcard akan mencoba dan mencocokkan semua file yang cocok dengan polanya. Jika Anda mencoba membuat tujuan umum "mencocokkan semuanya dan menyalinnya ke program apa pun selain menambahkan sufiks ini", maka ya, meletakkan metacharacter yang tidak terhindarkan sebagai target mungkin tidak akan melakukan apa yang Anda inginkan. Karena semua metakarakter di baris perintah shell diperluas. Jika Anda TAHU pasti bahwa target metacharacter tidak akan pernah membentuk kecocokan, Anda dapat menggunakannya- karena shell tidak dapat memperluasnya.
Mike S

... tapi itu akan menjadi hal yang jelek untuk dilakukan. Lebih baik menggunakan karakter khusus. % atau garis bawah itu bagus, mereka umumnya bukan metakarakter (tapi hati-hati menggunakan% dalam file crontab; itu spesial di sana).
Mike S

Jawaban:


14

Berikut adalah contoh dari satu penggunaan atipikal sed, yang berlaku untuk tugas ini:

sed -i.bak '' file-prefix*

Dengan cara ini, sebenarnya, sedtidak akan mengubah file, karena kami tidak memberikan perintah apa pun '', tetapi karena opsi -i[suffix]itu akan membuat salinan cadangan dari setiap file. Saya menemukan pendekatan ini ketika saya mencari. Apakah ada cara untuk membuat salinan cadangan file, tanpa mengetikkan namanya dua kali?


FYI: $ time sed -i.bak '' gmail-meta3*=real 0m0.069s
WinEunuuchs2Unix

Jika file sudah ada maka: real 0m0.037s. Jika file dihapus dan menjalankan waktu dekat kedua untuk cpkecepatan: real 0m0.051s.
WinEunuuchs2Unix

@xiota Poin yang menarik. Ternyata sedlebih cepat ketika file target sudah ada tetapi cplebih lambat ketika file target ada. Saya benar-benar membersihkan cache dan buffer daripada syncketika melakukan tes waktu yang besar tapi saya tidak melakukannya saat ini. Karena hal ini dapat menyimpang ke dalam opera sabun luar-topik yang sama sekali berbeda, saya menyesal membagikan hasil tes saya :( Apakah sudah terlambat untuk berpura-pura percakapan ini tidak pernah terjadi? FYI pesan gmail metadata ukuran 2,5 MB dan 3 file indeks sekitar 800 KB Juga bukan hard disk, ini adalah Samsung Pro 960 NVMe SSD pada 4 saluran
WinEunuuchs2Unix

1
Mungkin tidak masalah perangkat penyimpanan apa yang Anda miliki untuk pengujian ini, jika ini terjadi pada mesin Linux. Kernel sangat bagus dalam buffering file dalam memori. Itulah nilai "buff / cache" saat menggunakan freeperintah. Tulisan aktual ke perangkat dilakukan pada saat yang dipilih oleh algoritma yang memperhitungkan usia cache dan tekanan memori pada mesin. Jika Anda mencoba beberapa tes, maka file pertama yang dibaca akan keluar dari disk, tetapi yang berikutnya kemungkinan besar akan langsung keluar dari memori (lihat sync; echo 3 > /proc/sys/vm/drop_caches).
Mike S

Kemarin saya membuat beberapa tes dengan file besar lebih dari 2GB dan - ya, pendekatan ini relatif lambat daripada menggunakan cpperintah, tapi saya tidak bisa mengatakan ada perbedaan kinerja yang signifikan .
pa4080

13

Anda bisa menggunakan find:

find . -max-depth 1 -name 'gmail-meta3*' -exec cp "{}" "{}.bak" \;

Itu akan menemukan dalam direktori saat ini .semua file dengan nama yang cocok dengan pola glob (ingat tanda kutip tunggal di sekitar pola untuk mencegah globbing shell). Untuk setiap file yang ditemukan, itu akan muncul cpdari nama ke nama.bak. The \; pada akhirnya memastikan itu akan melakukan masing-masing file secara individual alih-alih melewati semuanya sekaligus. Kedalaman maksimal sebagai 1 hanya mencari direktori cuurent alih-alih berulang.


1
Apakah akan berfungsi dengan find . -max-depth 1 -name '"$1"'' -exec cp "{}" "{}$2" \;ketika $ 1 adalah sumber dan $ 2 adalah ekstensi?
WinEunuuchs2Unix

$ 2 harus ok untuk diganti asalkan masuk akal. $ 1 bisa lebih sulit karena kita tidak bisa melakukan substitusi variabel dalam tanda kutip tunggal. Saya tidak yakin begitu saja tetapi mungkin dapat menggunakan $ 1 dalam tanda kutip ganda karena polanya disimpan dalam sebuah string.
cbojar

11

Anda dapat menggunakan satu forlingkaran denganbash . Biasanya, saya cukup mengetiknya sebagai satu-liner karena ini bukan tugas yang sering saya lakukan:

for f in test* ; do cp -a "$f" "prefix-${f}.ext" ; done

Namun, jika Anda memerlukannya sebagai skrip:

cps() {
   [ $# -lt 2 ] && echo "Usage: cps REGEXP FILES..." && return 1

   PATTERN="$1" ; shift

   for file in "$@" ; do
      file_dirname=`dirname "$file"`
      file_name=`basename "$file"`
      file_newname=`echo "$file_name" | sed "$PATTERN"`

      if [[ -f "$file" ]] && [[ ! -e "${file_dirname}/${file_newname}" ]] ; then
         cp -a "$file" "${file_dirname}/${file_newname}"
      else
         echo "Error: $file -> ${file_dirname}/${file_newname}"
      fi
   done
}

Penggunaannya mirip dengan rename. Untuk mengetes:

pushd /tmp
mkdir tmp2
touch tmp2/test{001..100}     # create test files
ls tmp2
cps 's@^@prefix-@ ; s@$@.bak@' tmp2/test*    # create backups
cps 's@$@.bak@' tmp2/test*    # more backups ... will display errors
ls tmp2
\rm -r tmp2                   # cleanup
popd

FYI: $ time for f in gmail-meta3* ; do cp -a "$f" "${f}.bak" ; done=real 0m0.046s
WinEunuuchs2Unix

Um tidak, saya tidak ingin mengoptimalkan waktu. Ini adalah 0.046detik yang berarti 0 detik untuk persepsi manusia. Saya hanya mencoba menunjukkan bagaimana saya menguji jawaban yang diposting dan menyampaikan informasi menarik kepada pemirsa yang melihat sedperintah di atas. Atau setidaknya saya tertarik membandingkan seddengan cp....
WinEunuuchs2Unix

Solusi Anda dengan cplebih cepat daripada solusi dengan sedsekalipun. Jadi itu alasan untuk perayaan :)
WinEunuuchs2Unix

(1)  -aadalah operator uji non-standar. Kenapa tidak digunakan -e? (2) "Tidak dapat membuat direktori sementara." adalah pesan kesalahan yang agak menyesatkan. (3) Kenapa tidak pakai saja mktemp -d? (4) Anda harus menguji status keluar. Misalnya, Anda harus mengatakan ! mkdir "$FOLDER" && echo "Unable to create temporary directory." && return 1 atau  mkdir "$FOLDER" || { echo "Unable to create temporary directory."; return 1;}. Demikian juga untuk cpdan  rename(dan bahkan mungkin pushd, jika Anda ingin berhati-hati). … (Lanjutan)
G-Man Mengatakan 'Reinstate Monica'

(Lanjutan) ... (5) Arrggghhhh! Jangan katakan $@; katakan "$@". (5b)  Tidak perlu menggunakan {dan } ketika Anda referensi variabel cara Anda lakukan ( "${FOLDER}",  "${PATTERN}" dan  "${file}"); lakukan saja "$FOLDER",  "$PATTERN" dan  "$file". (6) Ini mengasumsikan bahwa file berada di direktori saat ini.  cps 's/$/.bak/' d/fooakan menyalin d/fooke foo.bak dalam direktori saat ini, bukan d/foo.bak.
G-Man Mengatakan 'Reinstate Monica'

6

Yang paling mungkin Anda dapatkan dari paradigma DOS adalah mcp(dari mmvpaket):

mcp 'gmail-meta3*' 'gmail-meta3#1.bak'

Jika zshtersedia, zmvmodul kontribusinya mungkin sedikit lebih dekat:

autoload -U zmv

zmv -C '(gmail-meta3*)' '$1.bak'

Saya akan menghindari ls- varian pada jawaban Anda sendiri yang aman untuk spasi putih (termasuk baris baru)

printf '%s\0' gmail-meta3* | while IFS= read -r -d '' f; do cp -a -- "$f" "$f.bak"; done

atau mungkin

printf '%s\0' gmail-meta3* | xargs -0 -I{} cp -a -- {} {}.bak

Saya mengerti mmvadalah paket tetapi dalam komentar Anda mengatakan perintah itu mcptetapi kemudian dalam perintah yang Anda gunakan mmvyang juga merupakan perintah dalam mmvpaket. Saya suka arah printfcontoh dan dalam skrip dipoles saya akan memastikan $ 1 dan $ 2 disahkan. +1 untuk mendapatkan bola bergulir :)
WinEunuuchs2Unix

@ WinEunuuchs2Unix minta maaf - mcp / mmv adalah brainfart. Sebenarnya mcpini hanya sinonim untukmmv -c
steeldriver

Jangan khawatir. Jika saya punya satu dolar untuk setiap kesalahan ketik yang saya buat saya akan menjadi seorang jutawan :) Saya ingin klarifikasi pada printfperintah saya tidak pernah menggunakan benar. Apakah Anda mengatakan printf '%s\0' "$1"*akan bekerja jika gmail-meta3dilewatkan sebagai parameter 1?
WinEunuuchs2Unix

@ WinEunuuchs2Unix Saya mungkin akan membiarkan konteks panggilan melakukan yaitu globbing cps gmail-meta3*dan kemudian menulis printf '%s\0"$ @" | sementara ... `dalam fungsi. Atau cukup gunakan for f; do cp -- "$f" "$f.bak"; done(seperti jawaban xiota , tetapi sebagai fungsi)
steeldriver

1
Perhatikan bahwa dengan zmvAnda dapat menggunakan mode "pengganti pengganti", yang menurut saya sedikit lebih mudah untuk dilakukan:zmv -W -C 'gmail-meta3*' '*.bak'
0x5453

5

solusi rsync saja

Jika Anda hanya ingin membuat cadangan file Anda, Anda dapat menyalinnya ke direktori baru

rsync /path/to/dir/Filename* /path/to/backupdirectory

Ini akan menyalin Filenamefile dari /path/to/dir/ke /path/to/backupdirectory.


rsync + filerename

Jika Anda ingin file cadangan Anda memiliki suffix, semuanya menjadi rusak dengan rsync...

rsync -Iu /path/to/dir/Filename* /path/to/dir/Filename* -b --backup-dir=/path/to/backupdirectory --suffix=.bak

Ini akan menimpa file yang ada ... dengan file yang ada ( -I) tetapi hanya jika mereka ( -u) lebih baru (yang tidak) dan membuat cadangan, dengan akhiran.

Anda juga dapat melakukannya di direktori yang sama. Tetapi lebih baik kecualikan cadangan yang ada.

rsync -Iu /path/to/dir/Filename* /path/to/dir/Filename* -b --backup-dir=/path/to/backupdirectory --suffix=.bak --exclude '*.bak'


Saya suka rsycncjadi saya memutakhirkannya tetapi, metode yang lebih sederhana adalah cp Filename* /path/to/backup/dirkarena file tidak perlu *.bakpemesat jika berada di direktori yang terpisah.
WinEunuuchs2Unix

4

Yang ini harus dilakukan seperti yang diminta:

cps(){ p="${@: -1}"; for f in "${@:1:$#-1}"; do cp -ai "$f" "${p//\?/$f}"; done  }

Pemakaian:

cps FILES... pattern
Example 1: cps gmail-meta3* ?.bak
Example 2: cps * save-?
Example 3: cps * bla-?-blubb

Saya memilih ?karena #harus dikutip ketika itu adalah karakter pertama dari pola, kalau tidak itu akan diakui sebagai awal dari komentar.

Uji:

$ touch 'test};{bla#?"blubb'
$ cps test* bla-?-blubb
$ ls
test};{bla#?"blubb  bla-test};{bla#?"blubb-blubb


Beberapa versi skrip sebelumnya untuk menambahkan akhiran:

Mirip dengan jawaban @ WinEunuuchs2Unix, tapi saya pikir lebih fleksibel dan tidak parsingls :

cps(){ S="$1"; shift; printf '%s\0' "$@" | xargs -0 -I{} cp -abfS "$S" {} {}; }

Letakkan ini di .bashrc.

Pemakaian:

cps SUFFIX FILES...
Example: cps .bak gmail-meta3*

Alternatif, dengan akhiran sebagai argumen terakhir ( via dan via ):

cps(){ S="${@: -1}"; printf '%s\0' "${@:1:$#-1}" | xargs -0 -I{} cp -abfS "$S" {} {}; }

Pemakaian:

cps FILES... SUFFIX
Example: cps gmail-meta3* .bak


Pengkodean yang bagus tapi agak sulit setelah beberapa dekade menggunakan Sumber lalu Target untuk mengalihkan perintah salin ke Target lalu Sumber
WinEunuuchs2Unix

Menambahkan fungsi dengan akhiran di belakang.
pLumo

Terima kasih, itu lebih intuitif. Menyebutnya suffix adalah akurat dengan cara jawaban saya mengkodekannya tetapi itu benar-benar target atau tujuan. Pengguna lain mungkin ingin menggunakan: copy gmail-meta3* old-meta3*. Dalam jawaban saya, saya tidak tahu bagaimana cara masuk *ke nama tujuan seperti pertanyaan saya yang diminta ...
WinEunuuchs2Unix

Masalahnya adalah yang *ditafsirkan oleh shell, jadi fungsinya tidak akan tahu tentang itu. Anda memerlukan beberapa karakter lain atau mengutipnya, lalu ganti dengan nama file asli dalam fungsi tersebut.
pLumo

Saya kira #bisa digunakan sebagai pengganti wildcard *? Jadi Anda bisa mengetik copy filenames# save-#. Saya pikir Anda ingin karakter wildcard sama untuk sumber dan target.
WinEunuuchs2Unix

4

Saya menulis ini satu baris ke dalam saya ~/.bashrc. Jawaban yang jauh lebih baik menggunakan finddapat saya kira. Bahkan jawaban yang lebih baik dapat ditulis dalam C. Mudah-mudahan T&J ini menggulirkan jawaban yang lebih baik:

cps () {
    # cps "Copy Splat", copy group of files to backup, ie "cps Filename .bak"
    # Copies Filename1 to Filename1.bak, Filename2 to Filename2.bak, etc.
    # If Filename1.bak exists, don't copy it to Filename1.bak.bak
    for f in "$1"*; do [[ ! "$f" == *"$2" ]] && cp -a "$f" "$f$2"; done

    # OLD version comments suggested to remove 
    # ls "$1"* | while read varname; do cp -a "$varname" "$varname$2"; done
}
  • for f in "$1"*; do: $1adalah gmail-meta3parameter dan fdaftar file yang cocok. Gabungan cara ini untuk gmail-meta3, gmail-meta3-LAB-9999, dll. Lakukan hal berikut
  • [[ ! "$f" == *"$2" ]] &&: $fsama seperti di fatas. $2adalah .bakparameter yang dilewati. Gabungan ini berarti jika nama file tidak berakhir .bak(karena kami tidak ingin menyalin .bakdan membuat .bak.bak) maka lakukan hal berikut
  • cp -a "$f" "$f$2"; salin gmail-meta3 ke gmail-meta3.bak, dll.
  • done: loop kembali dan ambil nama file berikutnya di gmail-meta3daftar *.

cps gmail-meta3 .bak Output Sampel

Menggunakan pertanyaan sebagai contoh di sini adalah bagaimana tampilannya dalam tindakan:

───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ ll gmail-meta3*
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3.bak
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick   728954 Jun 27 17:04 gmail-meta3-YAD-1558392194-26467821
-rw-rw-r-- 1 rick rick   728954 Jun 27 05:46 gmail-meta3-YAD-1558392194-26467821.bak
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ cps gmail-meta3 .bak
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ ll gmail-meta3*
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3.bak
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick   728954 Jun 27 17:04 gmail-meta3-YAD-1558392194-26467821
-rw-rw-r-- 1 rick rick   728954 Jun 27 17:04 gmail-meta3-YAD-1558392194-26467821.bak
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ 

Catatan: Ini menggunakan -abendera dengan cpperintah untuk menyimpan stempel waktu dan memberi Anda pemahaman yang lebih baik tentang cadangan file Anda.

Perhatikan bagaimana salinan file memiliki tanggal dan waktu yang sama persis seperti aslinya. Jika -aparameter dihilangkan mereka akan diberi tanggal dan waktu saat ini dan tidak akan terlihat seperti cadangan yang sebenarnya kecuali bahwa ukuran file akan sama.


6
jangan orang selalu merekomendasikan untuk parsingls
qwr

3
Karena Anda menyebutkan findsaya menganggap Anda sadar akan bahaya parsing ls? Tetapi dalam kasus Anda tidak diperlukan: lakukan saja for file in "$1"*; do copy -a "$file" "$file$2"; done- ini benar-benar aman dan jauh lebih sederhana daripada segala jenis tipuan melalui lsatau finddan satu whileloop.
Konrad Rudolph

@KonradRudolph Terima kasih atas saran Anda. Saya menerapkan dan menguji saran Anda dengan beberapa perubahan kecil.
WinEunuuchs2Unix

2

Metode lain untuk mencapai kebutuhan Anda adalah menyalin file ke direktori sementara dan menggunakan renameperintah untuk mengganti nama mereka.

$ mkdir backup
$ cp filename* /tmp/rename-backup/
$ rename 's/(filename.*)/$1.bak/' /tmp/rename-backup/*
$ mv /tmp/rename-backup/* ./

Jika Anda membutuhkannya sebagai skrip, Anda dapat menggunakannya seperti itu

cps () {
    mkdir -p /tmp/rename-backup/
    cp "$1"* /tmp/rename-backup/
    rename "s/($1.*)/\$1.$2/" /tmp/rename-backup/*
    mv "/tmp/rename-backup/$1"*".$2" .
}

Dan Anda bisa menggunakannya seperti ini:

cps file bak

Ini sebuah contoh

$ ls -l
total 0
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file a
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ab
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ac
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename1
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename2
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename3
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename4
$ cps file bak
$ ls -l
total 0
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file a
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 file a.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ab
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 file ab.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ac
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 file ac.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename1
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 filename1.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename2
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 filename2.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename3
-rw-r--r--  1 danny  wheel  0 Jun 26 16:41 filename3.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename4
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 filename4.bak
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.