Есть ли способ заставить 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 без .каталога ...
Jens

3
для новичков, --parents может быть сокращен до -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
Я единственный, кто понимает, что этот код не имеет смысла? Вы рекурсивно создаете абсолютный путь к существующему файлу (что означает, что этот путь уже существует), а затем перемещаете файл в местоположение (которое в вашем примере не существует).
vdegenne

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- так, чтобы я знал, когда я мог бы создавать каталоги.
Брайан Дункан

Похоже, лучшая идея решения, Butimplementation много сбоев.
Улисес Лейера

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, вы либо (а) захотите скопировать в ~ / 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 --- котенок экс-модератора

Если каталог не существует, зачем создавать его, если вы просто собираетесь его переместить? (установлен флаг -p)
Крис Латс

Я имею в виду, когда вы запускаете скрипт, а каталог $ 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, вероятно, является лучшим, хотя.


Да, немного глупо. :-) Если $ 2 - это каталог (как в исходном вопросе), то он с треском провалится, так как последний каталог будет удален, поэтому mv не удастся. И если $ 2 уже существует, он также пытается удалить его.
Тилла

Спасибо! Это именно то, что мне нужно было сделать: mv ./old ./subdir/new, где subdir еще не существует
Майк Мюррей,


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( командная строка, утилита поиска в виде простого текста ). Флаг направляет , чтобы найти и вернуть все имена файлов , исключающие . Этот список файлов добавляется в нашу командную строку как исходные аргументы команды перемещения . Аргумент target - это путь к новому каталогу, который передается по ссылке с использованием и выводится с помощью .
-vgrepdemo-app.zip
mvmkdir$_echo


1
Это хороший уровень детализации и объяснения - особенно для первого поста. Спасибо!
Джереми Кейни

Спасибо, @JeremyCaney! Я взволнован, чтобы начать помогать!
Эван Вундер

0

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

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • который создает 3 директории (directory1, directory2, directory3),
    • и в каждом из них два подкаталога (subdirectory1, subdirectory2),
      • и в каждом из них два подкаталога (subsubdirectory1 и subsubdirectory2).

Вы должны использовать 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.