Zmień nazwę wielu plików na podstawie wzorca w Uniksie

249

W katalogu znajduje się wiele plików rozpoczynających się od przedrostka fgh, na przykład:

fghfilea
fghfileb
fghfilec

Chcę zmienić nazwę wszystkich, aby rozpocząć od prefiksu jkl. Czy jest to jedno polecenie zamiast zmieniać nazwę każdego pliku osobno?

jww
źródło
1
sprawdź tutaj: theunixshell.blogspot.com/2013/01/…
Vijay
Powiązane pytanie: Lepszy sposób na zmianę nazwy plików na podstawie wielu wzorców .
Michael Le Barbier Grünewald

Odpowiedzi:

290

Jest kilka sposobów, ale korzystanie renamebędzie prawdopodobnie najłatwiejsze.

Korzystanie z jednej wersji rename:

rename 's/^fgh/jkl/' fgh*

Używając innej wersji rename(takiej samej jak odpowiedź Judy2K ):

rename fgh jkl fgh*

Powinieneś sprawdzić stronę podręcznika swojej platformy, aby zobaczyć, które z powyższych dotyczy.

Stephan202
źródło
7
+1 Nawet nie wiedziałem o zmianie nazwy ... Teraz mogę przestać używać pętli for z mv i sed ... Dzięki!
balpha
2
Linkujesz do innej, renamea następnie wyświetlasz składnię dla unixhelp.ed.ac.uk/CGI/man-cgi?nazwa jest druga
Hasturkun
7
Nie występuje we wszystkich systemach * nix. Nie dotyczy Max OS X i nie ma pakietu, który można pobrać. Nie spojrzałem na MacPorts.
dmckee --- były moderator kociąt
6
AFAICT, renamewydaje się być skryptem lub narzędziem specyficznym dla Linuksa. Jeśli w ogóle zależy Ci na przenośności, kontynuuj używanie sedi pętli lub wbudowanego skryptu Perla.
D.Shawley,
33
brew install renamena OS X :)
sam
117

Oto jak sedi mvmożna ich używać razem do zmiany nazwy:

for f in fgh*; do mv "$f" $(echo "$f" | sed 's/^fgh/jkl/g'); done

Jak w poniższym komentarzu, jeśli w nazwach plików są spacje, może być konieczne umieszczenie cudzysłowu wokół funkcji podrzędnej, która zwraca nazwę, aby przenieść pliki do:

for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
nik
źródło
1
Bardzo blisko. Zauważ, że chcesz dopasować tylko pierwsze wystąpienie fgh: 's / ^ fgh / jkl / g' (daszek robi różnicę).
Stephan202
2
Tylko ze względu na precyzję ... Masz na myśli „fgh na początku nazwy”, a nie „pierwsze wystąpienie fgh”. / ^ fgh / będzie pasować do „fghi”, ale nie do „efgh”.
Dave Sherohman
@Stephan, To była literówka z mojej strony (naprawiona).
nik
5
Jeśli nie masz dostępu do „zmiany nazwy”, działa to świetnie. Kod może wymagać cudzysłowów, jeśli nazwy plików zawierają spacje. for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
Dave Nelson
1
@nik bez cudzysłowów zmiana nazwy tej listy plików rzucał błąd: touch fghfilea fghfileb fghfilec fghfile\ d. Proponuję wziąć pod uwagę uwagi @DaveNelson.
luissquall
75

zmiana nazwy może nie być w każdym systemie. więc jeśli go nie masz, użyj powłoki w tym przykładzie w powłoce bash

for f in fgh*; do mv "$f" "${f/fgh/xxx}";done
ghostdog74
źródło
1
W tym rozwiązaniu sedpolecenie nie jest wymagane. Ten jest prostszy niż odpowiedź @ nik.
Halil
xxx oznacza zastępstwo? w przypadku oryginalnego plakatu byłby to „
jkl
1
Najczystsze rozwiązanie ze wszystkich.
MT Head
3
Być może bardziej zdecydowanie podkreślam, że jest to rozszerzenie Bash, które nie istnieje w POSIX shlub ogólnie w innych powłokach.
tripleee
Słodko - w świecie uniksowym jest tak wiele niejasnych zakątków - nigdy wcześniej tego nie widziałem.
wcochran
40

Używając mmv :

mmv "fgh*" "jkl#1"
Lee Netherton
źródło
1
Wow, jest to doskonały i prosty sposób rozwiązania problemu! Cieszę się, że wprowadzono Cię do MMV, dzięki!
Hendeca
1
Dzięki!!! Nigdy wcześniej nie słyszałem o mmv. Właśnie zainstalowany i bawię się nim - prosty, wydajny.
Nick Rice,
3
jeśli chcesz partii zmienić nazwę rekurencyjnie użyj ;w połączeniu z #1. przykład:mmv ";fgh*" "#1jkl#2"
Alp
Bardzo eleganckie rozwiązanie!
Mały uczeń Fermata
1
Dokładnie to, czego potrzebowałem. Przechwytuj grupy za pomocą „ ” i oddzwoń do nich za pomocą # 1, # 2, ... EX: mmv "my show ep 1080p. *" "My.show. # 1. # 2" = my.show.001.avi
Lundy,
20

Można to zrobić na wiele sposobów (nie wszystkie będą działać na wszystkich systemach unixy):

  • ls | cut -c4- | xargs -I§ mv fgh§ jkl§

    § może zostać zastąpiony dowolnym, co uznasz za wygodne. Możesz to zrobić find -execrównież, ale na wielu systemach zachowuje się to nieco inaczej, więc zwykle tego unikam

  • for f in fgh*; do mv "$f" "${f/fgh/jkl}";done

    Surowe, ale skuteczne, jak mówią

  • rename 's/^fgh/jkl/' fgh*

    Naprawdę ładne, ale zmiana nazwy nie jest obecna w BSD, który jest najczęściej spotykanym systemem unixowym.

  • rename fgh jkl fgh*

  • ls | perl -ne 'chomp; next unless -e; $o = $_; s/fgh/jkl/; next if -e; rename $o, $_';

    Jeśli nalegasz na używanie Perla, ale w twoim systemie nie ma żadnej zmiany nazwy, możesz użyć tego potwora.

Niektóre z nich są nieco skomplikowane, a lista jest daleka od ukończenia, ale znajdziesz tutaj to, czego chcesz dla prawie wszystkich systemów uniksowych.

iwein
źródło
2
kocham tego rodzaju potwora!
lefakir
tak, nadal mam słabą pozycję dla Perla :)
iwein
Potwór Perl tutaj działa bezbłędnie, jeśli masz spacje w nazwach plików.
mlerley,
13
rename fgh jkl fgh*
Judy2K
źródło
6
Na moim komputerze powoduje to błąd „Bareword„ fgh ”jest niedozwolony, gdy„ ścisłe napisy ”są używane w wierszu 1 (eval 1)”.
Stephan202
@ Stephan202, jaka jest twoja maszyna?
nik
Ubuntu 8.10 (perl v5.10.0 / 2009-06-26)
Stephan202
@ Stephan202 Mam ten sam problem, czy rozwiązałeś problem? (7 lat później)
pseudonim
1
@whossname: przepraszam, naprawdę nie pamiętam. (Powolna odpowiedź z powodu wakacji.)
Stephan202,
8

Korzystanie find, xargsi sed:

find . -name "fgh*" -type f -print0 | xargs -0 -I {} sh -c 'mv "{}" "$(dirname "{}")/`echo $(basename "{}") | sed 's/^fgh/jkl/g'`"'

Jest bardziej złożony niż rozwiązanie @ nik, ale pozwala rekursywnie zmieniać nazwy plików. Na przykład struktura

.
├── fghdir
│   ├── fdhfilea
│   └── fghfilea
├── fghfile\ e
├── fghfilea
├── fghfileb
├── fghfilec
└── other
    ├── fghfile\ e
    ├── fghfilea
    ├── fghfileb
    └── fghfilec

przekształciłby się w to,

.
├── fghdir
│   ├── fdhfilea
│   └── jklfilea
├── jklfile\ e
├── jklfilea
├── jklfileb
├── jklfilec
└── other
    ├── jklfile\ e
    ├── jklfilea
    ├── jklfileb
    └── jklfilec

Kluczem do jego działania xargsjest wywołanie powłoki z xargs .

luissquall
źródło
1
Zamierzałem zamieścić coś takiego, ale zajęło mi godzinę, aby uzyskać właściwe polecenie
Juan Mendes
3

Aby zainstalować skrypt zmiany nazwy Perla :

sudo cpan install File::Rename

Istnieją dwie nazwy, jak wspomniano w komentarzach w odpowiedzi Stephan202. Dystrybucje oparte na Debianie mają nazwę Perl . RedHat'a / rpm dystrybucje mieć C zmiany nazwy .
System OS X nie ma domyślnie zainstalowanego (przynajmniej w wersji 10.8), podobnie jak Windows / Cygwin.

Sam Inverso
źródło
3

Oto sposób na zrobienie tego za pomocą wiersza poleceń Groovy:

groovy -e 'new File(".").eachFileMatch(~/fgh.*/) {it.renameTo(it.name.replaceFirst("fgh", "jkl"))}'
jesseplymale
źródło
2

W systemie Solaris możesz wypróbować:

for file in `find ./ -name "*TextForRename*"`; do 
    mv -f "$file" "${file/TextForRename/NewText}"
done
Andriej Aleksandrow
źródło
Masz pół punktu do cytowania nazw plików wewnątrz pętli, ale for file in $(find)jest fundamentalnie błędna i nie mogą być korygowane z cytowania. W przypadku findzwrotów ./file name with spacesdostaniesz forpętlę ./file, name, with, i spaces, a nie ilość cytując wewnątrz pętli pomoże (lub nawet być konieczne).
tripleee
2
#!/bin/sh

#replace all files ended witn .f77 to .f90 in a directory

for filename in *.f77
do 
    #echo $filename
    #b= echo $filename | cut -d. -f1
    #echo $b    
    mv "${filename}" "${filename%.f77}.f90"    
done
Suragini
źródło
2

Ten skrypt działał dla mnie w przypadku rekurencyjnej zmiany nazw katalogów / plików, które prawdopodobnie zawierają białe spacje:

find . -type f -name "*\;*" | while read fname; do
    dirname=`dirname "$fname"`
    filename=`basename "$fname"`
    newname=`echo "$filename" | sed -e "s/;/ /g"`
    mv "${dirname}/$filename" "${dirname}/$newname"
done

Zwróć uwagę na sedwyrażenie, które w tym przykładzie zastępuje wszystkie wystąpienia ;spacją . Powinno to oczywiście zostać zastąpione stosownie do konkretnych potrzeb.

nielsen
źródło
Wielkie dzięki ! Świetny scenariusz
Walter Schrabmair,
1

Korzystanie z narzędzi StringSolver (Windows i Linux Bash ), które przetwarzają na przykładach:

filter fghfilea ok fghreport ok notfghfile notok; mv --all --filter fghfilea jklfilea

Najpierw oblicza filtr na podstawie przykładów , w których dane wejściowe to nazwy plików i dane wyjściowe (ok i notok, dowolne ciągi znaków). Gdyby filtr miał opcję --auto lub został wywołany sam po tym poleceniu, utworzyłby odpowiednio folder oki folder notoki wypchnąłby do nich pliki.

Następnie za pomocą filtra mvpolecenie jest półautomatycznym ruchem, który staje się automatyczny z modyfikatorem --auto. Korzystając z poprzedniego filtra dzięki opcji --filter, znajduje mapowanie od fghfileado, jklfileaa następnie stosuje je do wszystkich filtrowanych plików.


Inne rozwiązania jednowierszowe

Inne równoważne sposoby robienia tego samego (każda linia jest równoważna), więc możesz wybrać swój ulubiony sposób.

filter fghfilea ok fghreport ok notfghfile notok; mv --filter fghfilea jklfilea; mv
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter fghfilea "mv fghfilea jklfilea"
# Even better, automatically infers the file name
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter "mv fghfilea jklfilea"

Rozwiązanie wieloetapowe

Aby dokładnie sprawdzić, czy polecenia działają dobrze, możesz wpisać następujące polecenie:

filter fghfilea ok
filter fghfileb ok
filter fghfileb notok

a gdy masz pewność, że filtr jest dobry, wykonaj pierwszy ruch:

mv fghfilea jklfilea

Jeśli chcesz przetestować i użyć poprzedniego filtra, wpisz:

mv --test --filter

Jeśli transformacja nie jest tym, czego chciałeś (np. Nawet gdy mv --explainwidzisz, że coś jest nie tak), możesz wpisać, mv --clearaby zrestartować przenoszenie plików lub dodać więcej przykładów, mv input1 input2gdzie input1 i input2 są innymi przykładami

Kiedy jesteś pewny, po prostu pisz

mv --filter

i voila! Cała zmiana nazwy odbywa się za pomocą filtra.

ZASTRZEŻENIE: Jestem współautorem tego dzieła wykonanego do celów akademickich. Wkrótce może pojawić się funkcja produkująca bash.

Mikaël Mayer
źródło
1

O wiele łatwiej (na moim komputerze Mac) było to zrobić w Ruby. Oto 2 przykłady:

# for your fgh example. renames all files from "fgh..." to "jkl..."
files = Dir['fgh*']

files.each do |f|
  f2 = f.gsub('fgh', 'jkl')
  system("mv #{f} #{f2}")
end

# renames all files in directory from "021roman.rb" to "021_roman.rb"
files = Dir['*rb'].select {|f| f =~ /^[0-9]{3}[a-zA-Z]+/}

files.each do |f|
  f1 = f.clone
  f2 = f.insert(3, '_')
  system("mv #{f1} #{f2}")
end
Raymond Gan
źródło
1

Za pomocą zmiany nazwy :

$ renamer --find /^fgh/ --replace jkl * --dry-run

Usuń --dry-runflagę, gdy będziesz zadowolony, że wynik wygląda poprawnie.

Lloyd
źródło
1

Moja wersja zmiany nazw plików masowych:

for i in *; do
    echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh
mdev
źródło
Podoba mi się możliwość weryfikacji przed uruchomieniem skryptu
ardochhigh
Działa to wspaniale w nazwach plików zawierających spacje, cytaty lub inne metaznaki powłoki.
tripleee
1

Poleciłbym użyć własnego skryptu, który rozwiązuje ten problem. Ma także opcje zmiany kodowania nazw plików i konwertowania łączenia znaków diakrytycznych na znaki o nieskomplikowanym składzie, problem ten zawsze mam przy kopiowaniu plików z komputera Mac.

#!/usr/bin/perl

# Copyright (c) 2014 André von Kugland

# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

$help_msg =
"rename.pl, a script to rename files in batches, using Perl
           expressions to transform their names.
Usage:
    rename.pl [options] FILE1 [FILE2 ...]
Where options can be:
    -v                      Verbose.
    -vv                     Very verbose.
    --apply                 Really apply modifications.
    -e PERLCODE             Execute PERLCODE. (e.g. 's/a/b/g')
    --from-charset=CS       Source charset. (e.g. \"iso-8859-1\")
    --to-charset=CS         Destination charset. (e.g. \"utf-8\")
    --unicode-normalize=NF  Unicode normalization form. (e.g. \"KD\")
    --basename              Modifies only the last element of the path.
";

use Encode;
use Getopt::Long;
use Unicode::Normalize 'normalize';
use File::Basename;
use I18N::Langinfo qw(langinfo CODESET);

Getopt::Long::Configure ("bundling");

# ----------------------------------------------------------------------------------------------- #
#                                           Our variables.                                        #
# ----------------------------------------------------------------------------------------------- #

my $apply = 0;
my $verbose = 0;
my $help = 0;
my $debug = 0;
my $basename = 0;
my $unicode_normalize = "";
my @scripts;
my $from_charset = "";
my $to_charset = "";
my $codeset = "";

# ----------------------------------------------------------------------------------------------- #
#                                        Get cmdline options.                                     #
# ----------------------------------------------------------------------------------------------- #

$result = GetOptions ("apply" => \$apply,
                      "verbose|v+" => \$verbose,
                      "execute|e=s" => \@scripts,
                      "from-charset=s" => \$from_charset,
                      "to-charset=s" => \$to_charset,
                      "unicode-normalize=s" => \$unicode_normalize,
                      "basename" => \$basename,
                      "help|h|?" => \$help,
                      "debug" => \$debug);

# If not going to apply, then be verbose.
if (!$apply && $verbose == 0) {
  $verbose = 1;
}

if ((($#scripts == -1)
  && (($from_charset eq "") || ($to_charset eq ""))
  && $unicode_normalize eq "")
  || ($#ARGV == -1) || ($help)) {
  print $help_msg;
  exit(0);
}

if (($to_charset ne "" && $from_charset eq "")
  ||($from_charset eq "" && $to_charset ne "")
  ||($to_charset eq "" && $from_charset eq "" && $unicode_normalize ne "")) {
  $codeset = langinfo(CODESET);
  $to_charset = $codeset if $from_charset ne "" && $to_charset eq "";
  $from_charset = $codeset if $from_charset eq "" && $to_charset ne "";
}

# ----------------------------------------------------------------------------------------------- #
#         Composes the filter function using the @scripts array and possibly other options.       #
# ----------------------------------------------------------------------------------------------- #

$f = "sub filterfunc() {\n    my \$s = shift;\n";
$f .= "    my \$d = dirname(\$s);\n    my \$s = basename(\$s);\n" if ($basename != 0);
$f .= "    for (\$s) {\n";
$f .= "        $_;\n" foreach (@scripts);   # Get scripts from '-e' opt. #
# Handle charset translation and normalization.
if (($from_charset ne "") && ($to_charset ne "")) {
  if ($unicode_normalize eq "") {
    $f .= "        \$_ = encode(\"$to_charset\", decode(\"$from_charset\", \$_));\n";
  } else {
    $f .= "        \$_ = encode(\"$to_charset\", normalize(\"$unicode_normalize\", decode(\"$from_charset\", \$_)));\n"
  }
} elsif (($from_charset ne "") || ($to_charset ne "")) {
    die "You can't use `from-charset' nor `to-charset' alone";
} elsif ($unicode_normalize ne "") {
  $f .= "        \$_ = encode(\"$codeset\", normalize(\"$unicode_normalize\", decode(\"$codeset\", \$_)));\n"
}
$f .= "    }\n";
$f .= "    \$s = \$d . '/' . \$s;\n" if ($basename != 0);
$f .= "    return \$s;\n}\n";
print "Generated function:\n\n$f" if ($debug);

# ----------------------------------------------------------------------------------------------- #
#                 Evaluates the filter function body, so to define it in our scope.               #
# ----------------------------------------------------------------------------------------------- #

eval $f;

# ----------------------------------------------------------------------------------------------- #
#                  Main loop, which passes names through filters and renames files.               #
# ----------------------------------------------------------------------------------------------- #

foreach (@ARGV) {
  $old_name = $_;
  $new_name = filterfunc($_);

  if ($old_name ne $new_name) {
    if (!$apply or (rename $old_name, $new_name)) {
      print "`$old_name' => `$new_name'\n" if ($verbose);
    } else {
      print "Cannot rename `$old_name' to `$new_name'.\n";
    }
  } else {
    print "`$old_name' unchanged.\n" if ($verbose > 1);
  }
}
André von Kugland
źródło
2
Zwróć uwagę, że odradzane są odpowiedzi tylko do linku , więc odpowiedzi SO powinny być końcowym punktem poszukiwania rozwiązania (w porównaniu z kolejnym postojem referencji, które z czasem stają się nieaktualne). Rozważ dodanie tutaj autonomicznego streszczenia, zachowując link jako odniesienie.
kleopatra
Jak przewidywał @kleopatra , link dostałstale over time
y2k-shubham
1
@ y2k-shubham, już nie.
André von Kugland,
0

Działa to dla mnie przy użyciu wyrażenia regularnego:

Chciałem zmienić nazwę plików w ten sposób:

file0001.txt -> 1.txt
ofile0002.txt -> 2.txt 
f_i_l_e0003.txt -> 3.txt

użyj wyrażenia regularnego [az | _] + 0 * ([0-9] +. ) gdzie ([0-9] +. ) jest podciągiem grupowym do użycia w poleceniu rename

ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)/, arr) { print   arr[0]  " "  arr[1] }'|xargs  -l mv

Produkuje:

mv file0001.txt 1.txt
mv ofile0002.txt 2.txt
mv f_i_l_e0003.txt 3.txt

Inny przykład:

file001abc.txt -> abc1.txt
ofile0002abcd.txt -> abcd2.txt 

ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)([a-z]+)/, arr) { print   arr[0]  " "  arr[2] arr[1] }'|xargs  -l mv

Produkuje:

  mv file001abc.txt abc1.txt
  mv ofile0002abcd.txt abcd2.txt 

Ostrzeżenie, bądź ostrożny.

Steven Lizarazo
źródło
0

Napisałem ten skrypt, aby wyszukać wszystkie pliki .mkv rekurencyjnie zmieniając nazwy znalezionych plików na .avi. Możesz dostosować go do swoich potrzeb. Dodałem kilka innych rzeczy, takich jak uzyskanie katalogu plików, rozszerzenia, nazwy pliku ze ścieżki pliku, na wypadek gdybyś musiał odwołać się do czegoś w przyszłości.

find . -type f -name "*.mkv" | while read fp; do 
fd=$(dirname "${fp}");
fn=$(basename "${fp}");
ext="${fn##*.}";
f="${fn%.*}";
new_fp="${fd}/${f}.avi"
mv -v "$fp" "$new_fp" 
done;
David Okwii
źródło
0

Ogólny skrypt do uruchomienia sedwyrażenia na liście plików (łączy sedrozwiązanie z renamerozwiązaniem ):

#!/bin/sh

e=$1
shift

for f in $*; do
    fNew=$(echo "$f" | sed "$e")
    mv "$f" "$fNew";
done

Wywołaj, przekazując skryptowi sedwyrażenie, a następnie dowolną listę plików, podobnie jak wersja rename:

script.sh 's/^fgh/jkl/' fgh*
palswim
źródło
0

Możesz także użyć poniższego skryptu. bardzo łatwo uruchomić go na terminalu ...

// Zmień nazwę wielu plików jednocześnie

for file in  FILE_NAME*
do
    mv -i "${file}" "${file/FILE_NAME/RENAMED_FILE_NAME}"
done

Przykład:-

for file in  hello*
do
    mv -i "${file}" "${file/hello/JAISHREE}"
done
Sanjay Singh
źródło