Чи є спосіб змусити mv створити каталог, до якого потрібно перемістити, якщо він не існує?


275

Отже, якщо я перебуваю у своєму домашньому каталозі і хочу перенести foo.c до ~ / bar / baz / foo.c, але цих каталогів не існує, чи є якийсь спосіб автоматично створити ці каталоги, щоб вам доведеться лише набирати текст

mv foo.c ~/bar/baz/ 

і все вийшло б? Здається, ви могли б псевдонімувати mv до простого скрипту bash, який би перевірив, чи існують ці каталоги, і якщо не зателефонував би mkdir, а потім mv, але я подумав, що я перевірю, чи є у кого краща ідея.


Відповіді:


294

Як щодо цього однопластового (в bash):

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

Порушення цього:

mkdir --parents ./some/path

створює каталог (включаючи всі проміжні каталоги), після чого:

mv yourfile.txt $_

переміщує файл у цей каталог ($ _ розширюється до останнього аргументу, переданого до попередньої команди оболонки, тобто: новоствореного каталогу).

Я не впевнений, наскільки це буде працювати в інших оболонках, але це може дати вам кілька ідей щодо того, на що звернути увагу.

Ось приклад використання цієї методики:

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


1
Чому ./всюди одержимість зайвим ? Аргументи - це не команди PATH без .каталогу ...
Єнс

3
для новачків, - батьків і батьків може бути скорочено до -p
sakurashinken

8
Як не дивно, --parentsнедоступний на macOS 10.13.2. Вам потрібно користуватися -p.
Джошуа Пінтер

1
Не працює при переміщенні файлу, наприклад ./some/path/foo. У цьому випадку команда повинна бути: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
Я єдиний, хто зрозумів, що цей код не має сенсу? Ви створюєте рекурсивно абсолютний шлях до існуючого файлу (а це означає, що цей шлях вже існує), а потім переміщуєте файл у місце (яке у вашому прикладі не існує).
вдегенне

1
@ user544262772 GNU coreutils ' dirnameне вимагає, щоб аргумент існував, це чиста обробка рядків. Не можу говорити про інші дистрибуції. Нічого поганого з цим кодовим фактом.
TroyHurts

Це відповідь, яка найлегше автоматизується, я думаю. for f in *.txt; do mkdir -p `dirname /destination/${f//regex/repl}`; mv "$f" "/destination/${f//regex/repl}; done
Кайл

23

Збережіть як сценарій з назвою mv або mv.sh

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

Або покладіть в кінці файлу ~ / .bashrc як функцію, яка замінює mv за замовчуванням на кожному новому терміналі. Використання функції дозволяє bash зберігати її в пам'яті, замість того, щоб читати файл сценарію кожен раз.

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

Вони ґрунтуються на поданні Кріса Луца.


3
Це найбільш точно відповідає на питання ІМХО. Користувач хотів би вдосконаленої mvкоманди. Особливо корисно для сценаріїв, тобто вам не обов'язково потрібно запускати перевірки та виконувати mkdir -pбудь-який час, коли вам потрібно mv. Але оскільки я хотів би, щоб поведінка помилок за замовчуванням для mv, я змінив ім'я функції на mvp- так, щоб я знав, коли я міг би створювати каталоги.
Брайан Данкан

Здається, найкраща ідея рішення, але реалізація сильно руйнує.
Ulises Layera

2
@UlisesLayera Я змінив алгоритм, щоб зробити його більш надійним. Зараз воно не повинно зазнати аварій
вересня

Невже хтось може пояснити значення того, що [ ! -a "$dir" ]я проводив експерименти з правою половиною як істинною, так і хибною, обох оцінювали як істинну? Заради інших, з'являється tmp = "$ {tmp: -1}", щоб схопити останній символ файлу, щоб переконатися, що це не шлях (/)
bazz

@bazz Це слід робити "якщо $ dir не існує, тоді продовжуйте". На жаль, ви праві, є помилка при використанні "! -A", і я не знаю чому. Я трохи відредагував код і зараз повинен працювати.
Сеперо

13

Ви можете використовувати mkdir:

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

Простий сценарій, щоб зробити це автоматично (не перевірено):

#!/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 "$@"

Коли я запускаю "$ dirname ~? Bar / baz /", я отримую "/ home / dmckee / bar", чого тут не хочеться ...
dmckee --- кошеня колишнього модератора

@dmckee, Ах, ти маєш рацію. Будь-яка ідея, як це вирішити? Якщо ви вводите ~ / bar / baz, ви або (a) хочете скопіювати в ~ / bar / і перейменувати в baz, або (b) скопіювати в ~ / bar / baz /. Який кращий інструмент для роботи?
страгер

@dmckee, я використовував regexp / sed, щоб придумати рішення. Це працює на ваш смак? =]
страгер

Приємно, окрім $ 2 - це не останній аргумент, якщо $ # = 2. У мене є програма, la, яка друкує останній аргумент. Раніше я використовував її у версії команди cp (щоб додати поточний каталог, якщо останній аргумент не був каталогом). Навіть сучасні снаряди підтримують лише $ 1 .. $ 9; сценарій Perl може бути кращим.
Джонатан Леффлер

@Leffler, Неправда - я знаю, що bash підтримує більше 9 аргументів (використання $ {123} - це один метод). Я не знаю Perl, тому сміливо відповідайте самі. =]
страгер

7

Здається, що відповідь - ні :). Я не хочу створювати псевдонім або функцію просто для цього, часто тому, що це одноразово, і я вже в середині введення mvкоманди, але я знайшов щось, що добре працює для цього:

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

Якщо mvпомилка (dir не існує), вона зробить каталог (який є останнім аргументом попередньої команди, так і $_є). Тож просто запустіть цю команду, потім upповторно запустити її, і цей час mvповинен досягти успіху.


5

Наступний скрипт оболонки, можливо?

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

Це основна частина. Ви можете додати трохи, щоб перевірити аргументи, і, можливо, ви хочете, щоб поведінка змінилася, якщо місце призначення існує, або вихідний каталог існує, або не існує (тобто не перезаписуйте те, що не існує) .


1
За допомогою вашого коду переміщення не виконується, якщо каталог не існує!
страгер

1
І ви пропустили прапор "-p" на mkdir.
dmckee --- кошеня колишнього модератора

Якщо каталог не існує, навіщо його створювати, якщо ви просто збираєтеся його перемістити? (-прапор виправлений)
Кріс Луц

Я маю на увазі, коли ви запускаєте скрипт і каталог $ 2 не існує, він створюється, але файл не копіюється.
страгер

він дав просту конструкцію і додав інформацію про те, як її слід розширити. Навіщо це голосувати ?!
ypnos

5

Команда rsync може виконати трюк лише в тому випадку, якщо остання директорія в шляху призначення не існує, наприклад, для шляху призначення до пункту, ~/bar/baz/якщо він barіснує, але bazйого немає, тоді можна використовувати таку команду:

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)

У цьому випадку bazкаталог буде створений, якщо його не існує. Але якщо обидва barі bazне існують, rsync вийде з ладу:

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]

Тому в основному його слід безпечно використовувати rsync -av --remove-source-filesяк псевдонім для mv.


4

Найпростіший спосіб зробити це:

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

Припустимо, я завантажив PDF-файли, що знаходяться в моєму каталозі завантажень ( ~/download), і я хочу перемістити їх у каталог, який не існує (скажімо my_PDF).

Я наберу наступну команду (переконайтесь, що мій поточний робочий каталог ~/download):

mkdir my_PDF && mv *.pdf $_

Ви можете додати -pпараметр, mkdirякщо ви хочете створити підкаталоги саме так: (я повинен створити підкаталог з назвою python):

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

2

Дурніше, але робочий спосіб:

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

Створіть каталог з mkdir -p, включаючи тимчасовий каталог, який розділяє ім'я файла призначення, потім видаліть цей каталог імен файлів простим rmdir, а потім перемістіть файл до його нового пункту призначення. Я думаю, що відповідь із використанням dirname, мабуть, найкраща.


Yepp, ніби нерозумно. :-) Якщо $ 2 - це каталог (як і в оригінальному запитанні), то він виходить з ладу, оскільки останній каталог буде видалений, тому "mv" не вдасться. І якщо $ 2 вже існує, то він також намагається видалити її.
Tylla

Дякую! Це саме те, що мені потрібно було зробити: mv ./old ./subdir/new там, де subdir ще не існує
Майк Мюррей

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

1

Код:

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

Приклад:

аргументи: "d1" "d2 / sub"

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

1

Це переміститься foo.cдо нового каталогу bazз батьківським каталогом bar.

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

-pОпція mkdirбуде створювати проміжні каталоги по мірі необхідності.
Без -pусіх каталогів префікс шляху вже повинен існувати.

Все, що знаходиться всередині задників ``, виконується, а вихід повертається в рядку як частина вашої команди.
Оскільки mkdirнічого не повертає, до команди echo $_буде додано лише вихідний результат .

$_посилається на останній аргумент на раніше виконану команду.
У цьому випадку він поверне шлях до вашого нового каталогу ( ~/bar/baz/), переданого mkdirкоманді.


Я розпакував архів, не вказавши призначення, і хотів перемістити всі файли, за винятком demo-app.zipмого поточного каталогу, у новий каталог з назвою demo-app.
Наступний рядок робить трюк:

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

ls -Aповертає всі імена файлів, включаючи приховані файли ( за винятком неявних .та.. ).

Символ труби |використовується для передачі виводу lsкоманди в grep( командний рядок, утиліта пошуку простого тексту ). Прапор направляє , щоб знайти і повернути все імена файлів , що виключають . Цей список файлів додається до нашого командного рядка як вихідні аргументи до команди переміщення . Цільовим аргументом є шлях до нового каталогу, який пройшов до посилання з використанням та виводу з використанням .
-vgrepdemo-app.zip
mvmkdir$_echo


1
Це хороший рівень деталізації та пояснення - особливо для першого повідомлення. Дякую!
Джеремі Кейні

Дякую, @JeremyCaney! Я радий почати допомагати!
Еван Вундер

0

Ви навіть можете використовувати розширення для дужок:

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • який створює 3 каталоги (каталог1, каталог2, каталог3),
    • і в кожному з них два підкаталоги (підкаталог1, підкаталог2),
      • і в кожному з них по два підвіддіали (subsubdirectory1 та sububdirectory2).

Ви повинні використовувати bash 3.0 або новішу версію.


5
Цікаво, але не відповідає на питання ОП.
Олексій Фельдхендлер

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

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

1
щоб зробити його окремим сценарієм sh, просто замініть $ dest і $ src на $ 1 і $ 2
cab404

0

Моє одне рядкове рішення:

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

0

Ви можете ланцюжок команд: mkdir ~/bar/baz | mv foo.c ~/bar/baz/
або скрипт оболонки знаходиться тут:

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

1. Відкрийте будь-який текстовий редактор.
2. Скопіюйте та вставте сценарій оболонки
3. Збережіть його як mkdir_mv.sh
4. Введіть каталог свого сценарію: cd your/script/directory
5. Змініть режим файлу : chmod +x mkdir_mv.sh
6. Встановіть псевдонім : alias mv="./mkdir_mv.sh"
Тепер, коли ви запускаєте команду, що mvрухається, команда буде створена, якщо її немає.


0

На основі коментаря в іншій відповіді, ось моя функція оболонки.

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

Включіть це в .bashrc або подібне, щоб ви могли використовувати його скрізь.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.