Czy istnieje sposób, aby mv utworzył katalog, do którego ma zostać przeniesiony, jeśli nie istnieje?


275

Tak więc, jeśli jestem w katalogu domowym i chcę przenieść foo.c do ~ / bar / baz / foo.c, ale te katalogi nie istnieją, czy istnieje sposób automatycznego utworzenia tych katalogów, aby wystarczy wpisać

mv foo.c ~/bar/baz/ 

i wszystko się ułoży? Wygląda na to, że możesz użyć aliasu mv do prostego skryptu bash, który sprawdziłby, czy te katalogi istnieją, a gdyby nie, wywołałby mkdir, a następnie mv, ale pomyślałem, że sprawdzę, czy ktoś ma lepszy pomysł.


Odpowiedzi:


294

Co powiesz na ten jednowarstwowy (w bash):

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

Podział tego:

mkdir --parents ./some/path

tworzy katalog (w tym wszystkie katalogi pośrednie), po czym:

mv yourfile.txt $_

przenosi plik do tego katalogu ($ _ rozwija się do ostatniego argumentu przekazanego do poprzedniej komendy powłoki, tj: nowo utworzonego katalogu).

Nie jestem pewien, jak daleko to zadziała w innych powłokach, ale może dać ci kilka pomysłów na to, czego szukać.

Oto przykład z wykorzystaniem tej techniki:

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

32
Ładnie z „$ _”.
dmckee --- były moderator kociak

1
Dlaczego obsesja na punkcie zbędnych ./wszędzie? Argumenty nie są poleceniami w PATH bez .katalogu ...
Jens

3
dla początkujących, - rodziców można skrócić do -p
sakurashinken

8
Co dziwne, --parentsnie jest dostępny w systemie macOS 10.13.2. Musisz użyć -p.
Joshua Pinter,

1
Nie działa, jeśli przenosisz plik np ./some/path/foo. W takim przypadku polecenie powinno brzmieć: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
Tylko ja zdałem sobie sprawę, że ten kod nie ma sensu? Rekurencyjnie tworzysz ścieżkę bezwzględną do istniejącego pliku (co oznacza, że ​​ta ścieżka już istnieje), a następnie przenosisz plik do lokalizacji (która w twoim przykładzie nie istnieje).
vdegenne,

1
@ user544262772 GNU coreutils ' dirnamenie wymaga istnienia swojego argumentu, to jedynie manipulacja ciągami. Nie mogę mówić o innych dystrybucjach. Nie ma nic złego w tym błędzie kodu.
TroyHurts

Sądzę, że odpowiedź ta jest najłatwiejsza do zautomatyzowania. for f in *.txt; do mkdir -p `dirname /destination/${f//regex/repl}`; mv "$f" "/destination/${f//regex/repl}; done
Kyle

23

Zapisz jako skrypt o nazwie mv lub mv.sh

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

Lub umieść na końcu pliku ~ / .bashrc jako funkcję, która zastępuje domyślny mv na każdym nowym terminalu. Użycie funkcji pozwala bashowi zachować pamięć, zamiast za każdym razem czytać plik skryptu.

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

Są one oparte na przesłaniu Chrisa Lutza.


3
To odpowiada najdokładniej na pytanie IMHO. Użytkownik chciałby ulepszonego mvpolecenia. Jest to szczególnie przydatne w przypadku skryptów, tzn. Niekoniecznie chcesz uruchamiać kontrole i uruchamiać je w mkdir -pdowolnym momencie mv. Ale ponieważ chciałbym mieć domyślne zachowanie błędu mv, zmieniłem nazwę funkcji na mvp- aby wiedzieć, kiedy mogę tworzyć katalogi.
Brian Duncan

Wydaje się, że to najlepszy pomysł na rozwiązanie, ale wdrożenie często się psuje.
Ulises Layera,

2
@UlisesLayera Zmodyfikowałem algorytm, aby był bardziej niezawodny. Nie powinno się teraz rozbić
Sepero

Czy ktoś mógłby wyjaśnić znaczenie [ ! -a "$dir" ]przeprowadzonych przeze mnie eksperymentów z prawą połową, która jest prawdziwa i fałszywa, obie ocenione na prawdę… ??? Dla innych, tmp = "$ {tmp: -1}" wydaje się chwytać ostatni znak pliku, aby upewnić się, że nie jest to ścieżka (/)
bazz

@bazz Powinno działać „jeśli $ dir nie istnieje, to kontynuuj”. Niestety masz rację, podczas używania „! -A” wystąpił błąd i nie wiem dlaczego. Trochę zmodyfikowałem kod i powinienem już działać.
Sepero,

13

Możesz użyć mkdir:

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

Prosty skrypt, aby zrobić to automatycznie (nieprzetestowany):

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

Kiedy uruchamiam „$ dirname ~? Bar / baz /”, dostaję „/ home / dmckee / bar”, co nie jest tym, czego tu chcesz ...
dmckee --- były moderator kociak

@dmckee, Ah, masz rację. Masz pomysł, jak to rozwiązać? Jeśli wpiszesz ~ / bar / baz, albo (a) chcesz skopiować do ~ / bar / i zmienić nazwę na baz, lub (b) skopiować do ~ / bar / baz /. Jakie jest lepsze narzędzie do pracy?
strager

@dmckee, użyłem regexp / sed, aby znaleźć rozwiązanie. Czy to ci odpowiada? =]
strager

Fajnie, z wyjątkiem tego, że 2 $ nie jest ostatnim argumentem, chyba że $ # = 2. Mam program, la, który wypisuje swój ostatni argument. Używałem go w wersji polecenia cp (aby dodać bieżący katalog, jeśli ostatnim argumentem nie był katalog). Nawet współczesne powłoki obsługują tylko 1 .. 9 USD; skrypt Perla może być lepszy.
Jonathan Leffler

@Leffler, Nieprawda - Wiem, że bash obsługuje więcej niż 9 argumentów (jedną z metod jest użycie $ {123}). Nie znam Perla, więc odpowiedz sam. =]
strager

7

Wygląda na to, że odpowiedź brzmi nie :). Nie chcę tak naprawdę tworzyć aliasu ani func, aby to zrobić, często dlatego, że jest to jednorazowe i już jestem w trakcie pisania mvpolecenia, ale znalazłem coś, co działa dobrze:

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

Jeśli się mvnie powiedzie (katalog nie istnieje), utworzy katalog (który jest ostatnim argumentem do poprzedniego polecenia, tak $_też jest). Po prostu uruchom to polecenie, a następnie upuruchom je ponownie i tym razem mvpowinno się to udać.


5

Może następujący skrypt powłoki?

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

To podstawowa część. Możesz dodać trochę, aby sprawdzić argumenty, i możesz zmienić zachowanie, jeśli miejsce docelowe istnieje, katalog źródłowy istnieje lub nie istnieje (tj. Nie zastępuj czegoś, co nie istnieje) .


1
W przypadku kodu przeniesienie nie zostanie wykonane, jeśli katalog nie istnieje!
strager

1
I przegapiłeś flagę „-p” dla mkdir.
dmckee --- były kot moderator

Jeśli katalog nie istnieje, po co go tworzyć, jeśli zamierzasz go przenieść? (-p flaga naprawiona)
Chris Lutz

Mam na myśli, że po uruchomieniu skryptu i katalogu $ 2 nie istnieje, jest on tworzony, ale plik nie jest kopiowany.
strager

podał prostą konstrukcję i dodał informacje o tym, jak należy ją rozwinąć. Po co głosować ?!
ypnos

5

Komenda rsync może wykonać lewę tylko wtedy, gdy ostatni katalog w ścieżce docelowej nie istnieje, np. w przypadku ścieżki docelowej ~/bar/baz/if baristnieje, ale baznie istnieje , można użyć następującej komendy:

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)

W takim przypadku bazkatalog zostanie utworzony, jeśli nie istnieje. Ale jeśli obie bari baznie istnieją rsync zawiedzie:

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]

Zasadniczo powinien być bezpieczny rsync -av --remove-source-filesjako alias dla mv.


4

Najprostszym sposobem na to jest:

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

Załóżmy, że pobrałem pliki pdf znajdujące się w moim katalogu pobierania ( ~/download) i chcę przenieść je wszystkie do katalogu, który nie istnieje (powiedzmy my_PDF).

Wpiszę następujące polecenie (upewniając się, że mój bieżący katalog roboczy to ~/download):

mkdir my_PDF && mv *.pdf $_

Możesz dodać -popcję, mkdirjeśli chcesz utworzyć podkatalogi w następujący sposób: (przypuszczalnie chcę utworzyć podkatalog o nazwie python):

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

2

Sillier, ale działający sposób:

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

Utwórz katalog z mkdir -p zawierający katalog tymczasowy, który ma wspólną nazwę pliku docelowego, następnie usuń katalog nazwy pliku za pomocą prostego rmdir, a następnie przenieś plik do nowego miejsca docelowego. Myślę, że odpowiedź za pomocą dirname jest prawdopodobnie najlepsza.


Tak, trochę głupie. :-) Jeśli 2 $ jest katalogiem (jak w pierwotnym pytaniu), to kończy się niepowodzeniem, ponieważ ostatni katalog zostanie usunięty, więc „mv” się nie powiedzie. A jeśli 2 USD już istnieje, to próbuje również go usunąć.
Tylla,

Dzięki! Właśnie tego potrzebowałem: mv ./old ./subdir/new gdzie subdir jeszcze nie istnieje
Mike Murray

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

1

Kod:

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

Przykład:

argumenty: „d1” „d2 / sub”

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

1

Zostanie przeniesiony foo.cdo nowego katalogu bazz katalogiem nadrzędnym bar.

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

-pOpcja mkdirspowoduje utworzenie katalogów pośrednich zgodnie z wymaganiami.
Bez -pwszystkich katalogów w ścieżce prefiks musi już istnieć.

Wszystko w backticks ``jest wykonywane, a dane wyjściowe są zwracane w linii w ramach polecenia.
Ponieważ mkdirnic nie zwraca, tylko polecenie echo $_zostanie dodane do polecenia.

$_odwołuje ostatni argument do poprzednio wykonanej komendy.
W takim przypadku zwróci ścieżkę do nowego katalogu ( ~/bar/baz/) przekazanego do mkdirpolecenia.


Rozpakowałem archiwum bez podania miejsca docelowego i chciałem przenieść wszystkie pliki oprócz demo-app.zipmojego bieżącego katalogu do nowego katalogu o nazwie demo-app.
Następująca linia załatwia sprawę:

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

ls -Azwraca wszystkie nazwy plików, w tym pliki ukryte ( z wyjątkiem niejawnych .i.. ).

Symbol potoku |służy do potokowania danych wyjściowych lspolecenia grep( narzędzie wiersza polecenia, wyszukiwanie tekstowe ). Flag kieruje odnaleźć i zwrócić wszystkie nazwy plików z wyłączeniem . Ta lista plików jest dodawana do naszego wiersza poleceń jako argumenty źródłowe do polecenia move . Argument docelowy jest ścieżką do nowego katalogu przekazywanego do referencyjnego za pomocą i wyjściowego za pomocą .
-vgrepdemo-app.zip
mvmkdir$_echo


1
To niezły poziom szczegółowości i wyjaśnień - a zwłaszcza pierwszego postu. Dzięki!
Jeremy Caney

Dziękuję, @JeremyCaney! Jestem podekscytowany, że mogę zacząć pomagać!
Evan Wunder

0

Możesz nawet użyć rozszerzeń nawiasów:

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • który tworzy 3 katalogi (katalog1, katalog2, katalog3),
    • a w każdym z nich dwa podkatalogi (podkatalog1, podkatalog2),
      • a w każdym z nich dwa podkatalogi (podkatalog1 i podkatalog2).

Musisz użyć wersji bash 3.0 lub nowszej.


5
Interesujące, ale nie odpowiada na pytanie PO.
Alexey Feldgendler,

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

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

1
aby był samodzielnym skryptem sh, po prostu zamień $ dest i $ src na 1 $ i 2 $
cab404

0

Moje jedno stringowe rozwiązanie:

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

0

Możesz łączyć polecenia: mkdir ~/bar/baz | mv foo.c ~/bar/baz/
lub skrypt powłoki jest tutaj:

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

1. Otwórz dowolny edytor tekstu.
2. Skopiuj i wklej skrypt powłoki.
3. Zapisz go jako mkdir_mv.sh
4. Wejdź do katalogu skryptu: cd your/script/directory
5. Zmień tryb pliku : chmod +x mkdir_mv.sh
6. Ustaw alias : alias mv="./mkdir_mv.sh"
Teraz, za każdym razem, gdy uruchomisz polecenie, mvzostanie utworzony katalog przenoszenia, jeśli nie istnieje.


0

Na podstawie komentarza w innej odpowiedzi, oto moja funkcja powłoki.

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

Dodaj to w .bashrc lub podobnym, abyś mógł używać go wszędzie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.