Có cách nào để làm cho mv tạo thư mục sẽ được chuyển đến nếu nó không tồn tại?


275

Vì vậy, nếu tôi đang ở trong thư mục nhà của mình và tôi muốn chuyển foo.c sang ~ / bar / baz / foo.c, nhưng những thư mục đó không tồn tại, có một số cách để các thư mục đó tự động được tạo, vì vậy bạn sẽ chỉ phải gõ

mv foo.c ~/bar/baz/ 

và mọi thứ sẽ diễn ra như thế nào? Có vẻ như bạn có thể đặt bí danh cho một tập lệnh bash đơn giản để kiểm tra xem các thư mục đó có tồn tại không và nếu không sẽ gọi mkdir và sau đó là mv, nhưng tôi nghĩ tôi sẽ kiểm tra xem có ai có ý tưởng tốt hơn không.


Câu trả lời:


294

Làm thế nào về điều này một lót (trong bash):

mkdir --parents ./some/path/; mv yourfile.txt $_

Phá vỡ nó:

mkdir --parents ./some/path

tạo thư mục (bao gồm tất cả các thư mục trung gian), sau đó:

mv yourfile.txt $_

di chuyển tệp vào thư mục đó ($ _ mở rộng đến đối số cuối cùng được truyền cho lệnh shell trước đó, tức là: thư mục mới được tạo).

Tôi không chắc điều này sẽ hoạt động được bao xa trong các vỏ khác, nhưng nó có thể cung cấp cho bạn một số ý tưởng về những gì cần tìm kiếm.

Dưới đây là một ví dụ sử dụng kỹ thuật này:

$ > ls
$ > touch yourfile.txt
$ > ls
yourfile.txt
$ > mkdir --parents ./some/path/; mv yourfile.txt $_
$ > ls -F
some/
$ > ls some/path/
yourfile.txt

32
Đẹp với '$ _'.
dmckee --- ex-moderator mèo con

1
Tại sao nỗi ám ảnh với sự dư thừa ./ở khắp mọi nơi? Các đối số không phải là lệnh trong PATH mà không có .thư mục ...
Jens

3
đối với người mới, - có thể rút ngắn thành -p
sakurashinken

8
Điều kỳ lạ --parentslà không có sẵn trên macOS 10.13.2. Bạn cần sử dụng -p.
Joshua Pinter

1
Không hoạt động nếu di chuyển tệp, ví dụ ./some/path/foo. Trong trường hợp này, lệnh phải là:mkdir -p ./some/path/foo; mv yourfile.txt $_:h
hhbilly

63
mkdir -p `dirname /destination/moved_file_name.txt`  
mv /full/path/the/file.txt  /destination/moved_file_name.txt

2
Tôi là người duy nhất nhận ra mã này không có ý nghĩa? Bạn tạo đệ quy đường dẫn tuyệt đối đến tệp hiện có (có nghĩa là đường dẫn này đã tồn tại) và sau đó di chuyển tệp đến một vị trí (trong ví dụ của bạn không tồn tại).
vdegenne

1
@ user544262772 GNU coreutils ' dirnamekhông yêu cầu đối số của nó tồn tại, đó là thao tác chuỗi thuần túy. Không thể nói cho các bản phân phối khác. Không có gì sai với mã này afaict.
TroyHurts 16/03/18

Đây là câu trả lời dễ dàng tự động nhất, tôi nghĩ vậy. for f in *.txt; do mkdir -p `dirname /destination/${f//regex/repl}`; mv "$f" "/destination/${f//regex/repl}; done
Kyle

23

Lưu dưới dạng tập lệnh có tên mv hoặc mv.sh

#!/bin/bash
# mv.sh
dir="$2"
tmp="$2"; tmp="${tmp: -1}"
[ "$tmp" != "/" ] && dir="$(dirname "$2")"
[ -a "$dir" ] ||
mkdir -p "$dir" &&
mv "$@"

Hoặc đặt ở cuối tệp ~ / .bashrc của bạn dưới dạng hàm thay thế mv mặc định trên mỗi thiết bị đầu cuối mới. Sử dụng một chức năng cho phép bash giữ bộ nhớ, thay vì phải đọc tệp tập lệnh mỗi lần.

function mv ()
{
    dir="$2"
    tmp="$2"; tmp="${tmp: -1}"
    [ "$tmp" != "/" ] && dir="$(dirname "$2")"
    [ -a "$dir" ] ||
    mkdir -p "$dir" &&
    mv "$@"
}

Những điều này dựa trên sự đệ trình của Chris Lutz.


3
Điều này trả lời chính xác nhất IMHO. Người dùng muốn một mvlệnh nâng cao . Đặc biệt hữu ích cho kịch bản, tức là bạn không nhất thiết muốn chạy kiểm tra và chạy mkdir -pbất cứ lúc nào bạn cần sử dụng mv. Nhưng vì tôi muốn hành vi lỗi mặc định cho mv, tôi đã thay đổi tên hàm thành mvp- để tôi biết khi nào tôi có thể tạo thư mục.
Brian Duncan

Có vẻ như ý tưởng giải pháp tốt nhất, nhưng thực hiện sụp đổ rất nhiều.
Ulises Layera

2
@UlisesLayera Tôi đã sửa đổi thuật toán để làm cho nó mạnh mẽ hơn. Nó không nên sụp đổ ngay bây giờ
Sepero

Ai đó có thể vui lòng giải thích ý nghĩa của [ ! -a "$dir" ]tôi đã tiến hành thí nghiệm với một nửa đúng là đúng và sai, cả hai đều được đánh giá là đúng .. ??? Vì lợi ích khác, tmp = "$ {tmp: -1}" xuất hiện để lấy ký tự cuối cùng của tệp để đảm bảo đó không phải là đường dẫn (/)
bazz

@bazz Nên làm "nếu $ dir không tồn tại, thì hãy tiếp tục". Thật không may, bạn đã đúng, có một lỗi khi sử dụng "! -A" và tôi không biết tại sao. Tôi đã chỉnh sửa mã một chút, và bây giờ sẽ hoạt động.
Sepero

13

Bạn có thể sử dụng mkdir:

mkdir -p ~/bar/baz/ && \
mv foo.c ~/bar/baz/

Một tập lệnh đơn giản để thực hiện tự động (chưa được kiểm tra):

#!/bin/sh

# Grab the last argument (argument number $#)    
eval LAST_ARG=\$$#

# Strip the filename (if it exists) from the destination, getting the directory
DIR_NAME=`echo $2 | sed -e 's_/[^/]*$__'`

# Move to the directory, making the directory if necessary
mkdir -p "$DIR_NAME" || exit
mv "$@"

Khi tôi chạy "$ dirname ~? Bar / baz /", tôi nhận được "/ home / dmckee / bar", đó không phải là điều bạn muốn ở đây ...
dmckee --- người điều hành cũ đã chọn

@dmckee, À, bạn nói đúng. Bất kỳ ý tưởng về làm thế nào để giải quyết điều này? Nếu bạn nhập ~ / bar / baz, bạn (a) muốn sao chép vào ~ / bar / và đổi tên thành baz, hoặc (b) sao chép thành ~ / bar / baz /. Công cụ nào tốt hơn cho công việc?
strager

@dmckee, tôi đã sử dụng regrec / sed để đưa ra giải pháp. Nó hoạt động theo ý thích của bạn? =]
strager

Thật tuyệt, ngoại trừ $ 2 không phải là đối số cuối cùng trừ khi $ # = 2. Tôi có một chương trình, la, in ra đối số cuối cùng của nó. Tôi đã từng sử dụng nó trong một phiên bản của lệnh cp (để thêm thư mục hiện tại nếu đối số cuối cùng không phải là thư mục). Ngay cả các vỏ hiện đại chỉ hỗ trợ $ 1 .. $ 9; một kịch bản Perl có thể tốt hơn.
Jonathan Leffler

@Leffler, Không đúng - Tôi biết bash hỗ trợ hơn 9 đối số (sử dụng $ {123} là một phương thức). Tôi không biết Perl, vì vậy hãy tự trả lời. =]
strager

7

Nghe có vẻ như câu trả lời là không :). Tôi thực sự không muốn tạo một bí danh hoặc func chỉ để làm điều này, thường là vì nó là một lần và tôi đang ở giữa việc gõ mvlệnh, nhưng tôi đã tìm thấy một cái gì đó hoạt động tốt cho điều đó:

mv *.sh  shell_files/also_with_subdir/ || mkdir -p $_

Nếu mv thất bại (dir không tồn tại), nó sẽ tạo thư mục (là đối số cuối cùng cho lệnh trước đó, vì vậy $_nó cũng có). Vì vậy, chỉ cần chạy lệnh này, sau đó upđể chạy lại nó, và lần này mvsẽ thành công.


5

Kịch bản shell sau đây, có lẽ?

#!/bin/sh
if [[ -e $1 ]]
then
  if [[ ! -d $2 ]]
  then
    mkdir --parents $2
  fi
fi
mv $1 $2

Đó là phần cơ bản. Bạn có thể muốn thêm một chút để kiểm tra các đối số và bạn có thể muốn hành vi thay đổi nếu đích tồn tại hoặc thư mục nguồn tồn tại hoặc không tồn tại (nghĩa là không ghi đè lên thứ gì đó không tồn tại) .


1
Với mã của bạn, di chuyển không được thực hiện nếu thư mục không tồn tại!
strager

1
Và bạn đã bỏ lỡ cờ "-p" cho mkdir.
dmckee --- ex-moderator mèo con

Nếu một thư mục không tồn tại, tại sao bạn tạo nó nếu bạn sẽ di chuyển nó? (-p cờ đã được sửa)
Chris Lutz

Ý tôi là, khi bạn chạy tập lệnh và thư mục $ 2 không tồn tại, nó được tạo nhưng tệp không được sao chép.
strager

ông đã đưa ra một cấu trúc đơn giản và thêm thông tin về cách mở rộng nó. Tại sao bỏ phiếu xuống?!
ypnos

5

Lệnh rsync chỉ có thể thực hiện thủ thuật nếu thư mục cuối cùng trong đường dẫn đích không tồn tại, ví dụ: đối với đường dẫn đích của ~/bar/baz/if bartồn tại nhưng bazkhông, thì có thể sử dụng lệnh sau:

rsync -av --remove-source-files foo.c ~/bar/baz/

-a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
-v, --verbose               increase verbosity
--remove-source-files   sender removes synchronized files (non-dir)

Trong trường hợp này, bazthư mục sẽ được tạo nếu nó không tồn tại. Nhưng nếu cả hai barbazkhông tồn tại rsync sẽ thất bại:

sending incremental file list
rsync: mkdir "/root/bar/baz" failed: No such file or directory (2)
rsync error: error in file IO (code 11) at main.c(657) [Receiver=3.1.2]

Vì vậy, về cơ bản nó nên an toàn để sử dụng rsync -av --remove-source-filesnhư một bí danh cho mv.


4

Cách đơn giản nhất để làm điều đó là:

mkdir [directory name] && mv [filename] $_

Giả sử tôi đã tải xuống các tệp pdf nằm trong thư mục tải xuống của mình ( ~/download) và tôi muốn chuyển tất cả chúng vào một thư mục không tồn tại (giả sửmy_PDF ).

Tôi sẽ gõ lệnh sau (đảm bảo thư mục làm việc hiện tại của tôi là ~/download):

mkdir my_PDF && mv *.pdf $_

Bạn có thể thêm -ptùy chọn mkdirnếu bạn muốn tạo thư mục con như thế này: (giả sử tôi muốn tạo thư mục con có tên python):

mkdir -p my_PDF/python && mv *.pdf $_

2

Sillier, nhưng cách làm việc:

mkdir -p $2
rmdir $2
mv $1 $2

Tạo thư mục với mkdir -p bao gồm một thư mục tạm thời chia sẻ tên tệp đích, sau đó xóa thư mục tên tệp đó bằng một rmdir đơn giản, sau đó di chuyển tệp của bạn đến đích mới. Tôi nghĩ rằng câu trả lời bằng cách sử dụng dirname có lẽ là tốt nhất mặc dù.


Yepp, hơi ngớ ngẩn .: -) Nếu $ 2 là một thư mục (như trong câu hỏi ban đầu) thì nó thất bại thảm hại, vì thư mục cuối cùng sẽ bị xóa, vì vậy 'mv' sẽ thất bại. Và nếu $ 2 đã tồn tại thì nó cũng cố gắng xóa nó.
Tylla

Cảm ơn! Đây chính xác là những gì tôi cần thực hiện: mv ./old ./subdir/new nơi subir chưa tồn tại
Mike Murray

2
((cd src-path && tar --remove-files -cf - files-to-move) | ( cd dst-path && tar -xf -))

1

Mã số:

if [[ -e $1 && ! -e $2 ]]; then
   mkdir --parents --verbose -- "$(dirname -- "$2")"
fi
mv --verbose -- "$1" "$2"

Thí dụ:

đối số: "d1" "d2 / phụ"

mkdir: created directory 'd2'
renamed 'd1' -> 'd2/sub'

1

Điều này sẽ di chuyển foo.cđến thư mục mới bazvới thư mục cha bar.

mv foo.c `mkdir -p ~/bar/baz/ && echo $_`

Các -ptùy chọn để mkdirsẽ tạo thư mục trung gian theo yêu cầu.
Không có-p tất cả các thư mục trong tiền tố đường dẫn phải tồn tại.

Mọi thứ bên trong backticks ``được thực thi và đầu ra được trả về trong dòng như một phần của lệnh của bạn.
mkdirkhông trả về bất cứ thứ gì, chỉ có đầu ra của echo $_sẽ được thêm vào lệnh.

$_tham chiếu đối số cuối cùng đến lệnh được thực hiện trước đó.
Trong trường hợp này, nó sẽ trả về đường dẫn đến thư mục mới của bạn ( ~/bar/baz/) được chuyển đếnmkdir lệnh.


Tôi đã giải nén một kho lưu trữ mà không đưa ra đích và muốn di chuyển tất cả các tệp ngoại trừ demo-app.ziptừ thư mục hiện tại của tôi sang một thư mục mới được gọi demo-app.
Dòng sau đây thực hiện thủ thuật:

mv `ls -A | grep -v demo-app.zip` `mkdir -p demo-app && echo $_`

ls -Atrả về tất cả các tên tệp bao gồm các tệp ẩn ( ngoại trừ ẩn ... ).

Biểu tượng ống |được sử dụng để dẫn đầu ra của lslệnh tới grep( một tiện ích tìm kiếm dòng lệnh, văn bản thuần túy ).
Các -vlá cờ chỉ đạo grepđể tìm và trả lại toàn bộ tên file trừ demo-app.zip.
Danh sách các tệp đó được thêm vào dòng lệnh của chúng tôi dưới dạng đối số nguồn cho lệnh di chuyển mv. Đối số đích là đường dẫn đến thư mục mới được chuyển đến mkdirtham chiếu bằng cách sử dụng $_và đầu ra bằng cách sử dụng echo.


1
Đây là một mức độ chi tiết và giải thích tốt đẹp và đặc biệt là cho một bài viết đầu tiên. Cảm ơn!
Jeremy Caney

Cảm ơn bạn, @JeremyCaney! Tôi rất vui mừng khi bắt đầu giúp đỡ!
Evan Wunder

0

Bạn thậm chí có thể sử dụng phần mở rộng niềng răng:

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • trong đó tạo ra 3 thư mục (thư mục1, thư mục2, thư mục 3),
    • và trong mỗi một trong hai thư mục con (thư mục con1, thư mục con2),
      • và trong mỗi người trong số họ có hai tiểu đơn vị (subsubdirectory1 và subsubdirectory2).

Bạn phải sử dụng bash 3.0 hoặc mới hơn.


5
Thú vị, nhưng không trả lời câu hỏi của OP.
Alexey Feldgendler

0
$what=/path/to/file;
$dest=/dest/path;

mkdir -p "$(dirname "$dest")";
mv "$what" "$dest"

1
để làm cho tập lệnh sh độc lập, chỉ cần thay thế $ Dest và $ src bằng $ 1 và $ 2
cab404

0

Giải pháp một chuỗi của tôi:

test -d "/home/newdir/" || mkdir -p "/home/newdir/" && mv /home/test.txt /home/newdir/

0

Bạn có thể xâu chuỗi các lệnh: mkdir ~/bar/baz | mv foo.c ~/bar/baz/
hoặc shell script ở đây:

#!/bin/bash
mkdir $2 | mv $1 $2

1. Mở bất kỳ trình soạn thảo văn bản
2. Sao chép-dán kịch bản shell.
3. Lưu nó dưới dạng mkdir_mv.sh
4. Nhập thư mục tập lệnh của bạn: cd your/script/directory
5. Thay đổi chế độ tập tin : chmod +x mkdir_mv.sh
6. Đặt bí danh : alias mv="./mkdir_mv.sh"
Bây giờ, bất cứ khi nào bạn chạy mvthư mục di chuyển lệnh sẽ được tạo nếu không tồn tại.


0

Dựa trên một nhận xét trong một câu trả lời khác, đây là chức năng shell của tôi.

# mvp = move + create parents
function mvp () {
    source="$1"
    target="$2"
    target_dir="$(dirname "$target")"
    mkdir --parents $target_dir; mv $source $target
}

Bao gồm điều này trong .bashrc hoặc tương tự để bạn có thể sử dụng nó ở mọi nơi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.