Apa yang dilakukan `mv ./*` tanpa menentukan tujuan?

27

Saya tidak sengaja lupa menentukan tujuan sebelum menekan tombol Return. Di mana mv ./*tanpa menentukan tujuan memindahkan file dan direktori di bawah direktori saat ini?

Tim
sumber
4
"Aku terkejut mvmenerima 1 argumen." Tidak. Ia menerima semua argumen yang diberikan shell setelah memperluas *.
glglgl

Jawaban:

41

Jika argumen terakhir adalah direktori, Anda baru saja memindahkan semua file dan direktori di direktori kerja Anda saat ini (kecuali yang namanya dimulai dengan titik) ke direktori itu. Jika ada dua file, file pertama mungkin telah menimpa file kedua.

Berikut ini beberapa demonstrasi:

Lebih dari dua file dan argumen terakhir adalah file

$ mkdir d1 d2 d3
$ touch a b c e
$ mv *
mv: target 'e' is not a directory

Lebih dari dua file dan argumen terakhir adalah direktori

$ mkdir d1 d2 d3
$ touch a b c
$ mv -v *
'a' -> 'd3/a'
'b' -> 'd3/b'
'c' -> 'd3/c'
'd1' -> 'd3/d1'
'd2' -> 'd3/d2'

Dua file

$ touch a b
$ mv -v *
'a' -> 'b'

Penjelasan lebih lanjut

Shell memperluas glob ( *) menjadi argumen untuk mv. Glob biasanya diperluas dalam urutan abjad. mvselalu melihat daftar file dan direktori. Ia tidak pernah melihat gumpalan itu sendiri.

Perintah ini mvmendukung dua jenis pemindahan. Satu adalah mv file ... directory. Yang lainnya adalah mv old-file-name new-file-name(atau mv old-file-name directory/new-file-name).

terdon
sumber
30

Pertama saya akan membuat basis tes - 5 file dan satu folder:

touch file1 file2 file3 file4 file5
mkdir folder

Selanjutnya saya akan menjalankan perintah tes. The -vmenspesifikasikan pilihan yang saya ingin setiap perintah mengeksekusi shell yang akan dicetak ke stderr. The -xpilihan menentukan bahwa saya menginginkan hal yang sama dicetak ke stderr- tapi aku ingin itu dilakukan setelah perintah dievaluasi tapi sebelum shell berjalan itu.

sh -cxv 'echo mv *'

KELUARAN

echo mv *
+ echo mv file1 file2 file3 file4 file5 folder
mv file1 file2 file3 file4 file5 folder

Jadi Anda melihat bahwa perintah yang saya beri makan adalah shell echo mv *dan perintah yang dijalankan oleh shell setelah * diperluas echo mvdiikuti oleh semua file dan folder.

Secara default shell akan memperluas gumpalan seperti:

sh -cxv 'echo file[1-5]'

KELUARAN

echo file[1-5]
+ echo file1 file2 file3 file4 file5
file1 file2 file3 file4 file5

Ini adalah hasil dari set [+-]ffungsi glob:

sh -cxvf 'echo file[1-5]'

KELUARAN

echo file[1-5]
+ echo 'file[1-5]'
file[1-5]

Jadi, ketika Anda menjalankan perintah di shell yang dikonfigurasi dengan opsi default seperti mv *shell memperluas ke *kata daftar argumen semua file di direktori saat ini diurutkan sesuai dengan lokal. Itu melakukan syscall exec(ve)untuk mv (pada dasarnya) dengan daftar argumen ini ditambahkan. Jadi mvdapatkan semua argumen saat shell menggumpal dan mengurutkannya. Selain melakukan straceuntuk melihat efek ini, Anda dapat menggunakan debug lagi seperti:

sh -s -- mv * <<\SCRIPT
sed -n l /proc/$$/cmdline
echo "$@"
SCRIPT

KELUARAN

sh\000-s\000--\000mv\000file1\000file2\000file3\000file4\000file5\000folder\
\000$
mv file1 file2 file3 file4 file5 folder

Dan mudah dibawa:

( PS4= IFS=/; set -x mv *; : "/$*/" ) 2>&1

KELUARAN

: /mv/file1/file2/file3/file4/file5/folder/

Pada dasarnya, shell mengeksekusi mvdengan isi direktori (jika tidak kosong dan tidak termasuk file / folder dengan nama yang dimulai dengan .) sebagai daftar argumennya. mvadalah POSIX ditentukan untuk menafsirkan argumen akhir sebagai direktori jika dipanggil dengan lebih dari dua argumen - dengan cara yang sama lnadalah (karena, pada kenyataannya, mereka alat yang sangat serupa dalam mendasari fungsi) .

Cukup echoes saja:

sh -cxv 'mv *' ; ls

KELUARAN

mv *
+ mv file1 file2 file3 file4 file5 folder
folder/

Semua file dipindahkan ke argumen akhir - karena itu adalah folder. Sekarang bagaimana jika itu bukan folder?

sh -cxv 'cd *; mv *'; ls . *

KELUARAN

cd *; mv *
+ cd folder
+ mv file1 file2 file3 file4 file5
mv: target ‘file5’ is not a directory

.:
folder/

folder:
file1  file2  file3  file4  file5

Beginilah cara POSIX menentukan mv perilaku dalam kasus itu:

mv [-if] source_file target_file

mv [-if] source_file... target_dir

Dalam bentuk sinopsis pertama, mvutilitas akan memindahkan file yang dinamai oleh operan source_file ke tujuan yang ditentukan oleh target_file . Bentuk sinopsis pertama ini diasumsikan ketika operan terakhir tidak menamai direktori yang ada dan bukan tautan simbolis yang merujuk ke direktori yang ada. Dalam hal ini, jika source_file menamai file non-direktori dan target_file diakhiri dengan /slashkarakter tambahan, mvakan menganggap ini sebagai kesalahan dan tidak ada operan source_file yang akan diproses.

Dalam bentuk sinopsis kedua, mvharus memindahkan setiap file yang dinamai oleh operan source_file ke file tujuan di direktori yang ada yang dinamai oleh operan target_dir , atau dirujuk jika target_dir adalah tautan simbolik yang merujuk ke direktori yang ada. Jalur tujuan untuk setiap source_file harus merupakan gabungan dari direktori target, /slashkarakter tunggal jika target tidak diakhiri dengan /slash, dan komponen pathname terakhir dari source_file . Bentuk kedua ini diasumsikan ketika operan terakhir menamai direktori yang ada.

Jadi jika *diperluas ke:

  • dua file

    • Anda seharusnya hanya memiliki satu file, yang merupakan file pertama renamedhingga yang kedua setelah yang kedua unlinked.
  • satu atau lebih file diikuti terakhir oleh direktori atau tautan ke satu

    • Anda seharusnya hanya memiliki satu direktori atau tautan ke satu, yang merupakan tempat di mana semua konten orang tuanya sebelumnya baru saja dipindahkan.
  • ada yang lain

    • Anda harus memiliki pesan kesalahan dan napas lega yang memuaskan.
mikeserv
sumber
1
ya, menggunakan yang lama sh, saya lupa tentang debug dengan itu, tetapi mengenai pertanyaan awal saya, itu berarti masalahnya adalah shell bukan mv? Itu menjijikkan, ini lebih sederhana dari yang saya pikirkan, tetapi itu adalah jebakan yang nyata.
user2485710
5
@ user2485710, titik kuncinya adalah bahwa di Unix, shell bertanggung jawab untuk memperluas wildcard sebelum meneruskan argumen baris perintah ke perintah. Di Windows, setiap perintah harus memperluas wildcard itu sendiri. Ini memungkinkan Unix shells untuk menawarkan fasilitas wildcard canggih yang berfungsi dengan perintah apa pun.
cjm
2
@ mikeserv mengingat fakta bahwa file-file itu tidak begitu penting. Aku bergegas membersihkan dan melompat ke langkah berikutnya, tapi aku bisa mengkonfirmasi hal-hal seperti sekarang muncul di edit posting pertama / pertanyaan saya.
user2485710
6
@ mikeserv tl; dr, *bukan satu argumen? Kanan? :)
Bernhard
1
@ Bernhard - sebenarnya, itu satu-satunya kasus yang tidak saya bahas - karena mungkin saja.
mikeserv
18

Pertama, shell mengembang ./*ke semua file di direktori saat ini (kecuali file yang dimulai dengan titik).

  • jika tidak ada atau hanya satu file: mvgagal
  • jika ada dua file: yang pertama dipindahkan ke yang kedua (yang karenanya hilang)
  • jika ada lebih dari dua file:
    • jika yang terakhir adalah direktori: semua file dipindahkan ke direktori ini
    • jika tidak, mvgagal.
Jofel
sumber
1
Terima kasih. bagaimana cara mengetahui subdirektori mana yang "yang terakhir"?
Tim
7
Panggil saja echo ./*apakah melihat urutan menggunakan shell Anda (biasanya alfabet).
jofel
Jika gagal dengan satu file mengapa tidak dikatakan demikian?
Noumenon
@ Noumenon Saya tidak mengerti komentar Anda. mvmengembalikan pesan kesalahan jika dipanggil hanya dengan satu argumen.
jofel
Kamu benar. Perintah yang sama dari sejarah saya sekarang memberi missing destination file operand after *filename*meskipun kemarin tidak.
Noumenon
4

Saat Anda mengetik mv ./*, shell Anda akan mengembang ./*sebelum mengeksekusi mv.

Beberapa hal yang perlu diperhatikan:

  • Jika ./*diperluas menjadi kurang dari 2 argumen, mvakan, secara logis, menghasilkan kesalahan.
  • ./* biasanya akan diperluas ke setiap file (termasuk direktori) yang ada di direktori saat ini dan tidak dimulai dengan titik.
  • Anda dapat mengontrol ./*ekspansi apa dengan membaca dokumentasi shell Anda ( man 7 globmerupakan titik masuk ke topik). Kerang yang berbeda akan memiliki opsi yang berbeda.
rahmu
sumber
1

Apa yang mv *harus dilakukan

Inilah jawaban yang lebih singkat:

Shell memperluas wildcard *ke daftar isi direktori. Kemudian shell melewati daftar lengkap itu ke perintah. Perintah itu tidak pernah melihat *.

Perintah mv file1 file2 ... filen directoryakan memindahkan file1 ... filen ke direktori.

Contoh

Di sini saya membuat direktori pengujian yang berisi tiga file

$ mkdir t
$ cd t
$ echo a>a; echo b>b; echo c>c
$ ls
a  b  c

Anda tidak dapat memindahkan banyak file ke satu file

$ mv *
mv: target `c' is not a directory

Mari kita tambahkan subdirektori

$ mkdir d

Anda dapat memindahkan banyak file ke subdirektori

$ mv *
$ ls
d
$ ls d
a  b  c
RedGrittyBrick
sumber
Perintah mv file1 file2 ... filen directoryini sangat kecil kemungkinannya ada hubungannya sama sekali *.
mikeserv
@ Mike: Jawaban saya menunjukkan bahwa tahap terakhir ekspansi shell secara efektif mengubah yang terakhir menjadi yang sebelumnya. Tidak mengetahui ini tampaknya menjadi akar kebingungan OP.
RedGrittyBrick
ya, tapi poin saya adalah shell tidak akan memperluas *ke file dirkecuali ada beberapa lokal di mana dberikut f.
mikeserv
@ mikeserv: Ada lokal seperti itu, contoh saya adalah cut & paste dari Putty terhubung ke sistem CentOS 5.6 GNU / Linux.
RedGrittyBrick
lokal mana itu? Contoh Anda adalah a b c dyang tidak file ... dir. Saya hanya berkomentar sama sekali karena Anda tidak menyebutkan hal semacam itu di mana pun dan hanya mengatakan itu *menjadi file ... diryang tidak terjadi.
mikeserv