How can I use ms-dos style wildcards with ls and mv?
Solution 1
One of the fundamental differences between Windows cmd
and POSIX shells is who is responsible for wildcard expansions. Shells do all the expansions required before starting the actual commands you asked for. cmd
mostly passes the wildcard patterns to the commands unmodified. (I say mostly, since I think there are exceptions, and environment variables are expanded under most circumstances.) This makes writing a rename
that would work with the same syntax as in cmd
quite tricky.
But there is a rename
for Linux - with completely different arguments, check out the man page (which is a bit terse on my system, and rename
comes from the util-linux
package on my system, which should be widely available). Your first rename would be done like this:
rename .txt .bak *.txt
Note that the shell does the *
expansion, so rename
itself actually thinks it was invoked like this:
rename .txt .bak file1.txt file2.txt file3.txt ...
So you can guess the single file version:
rename .txt .bak file1.txt
If you don't want to use rename
but implement this yourself, you could create a function for that. Assuming you only want to change the file extension, and for single-file rename, look at this:
$ function chext() {
newext="$1"
file="$2"
newfile="${file%.*}$newext"
echo mv "$file" "$newfile"
}
$ chext .csv test.txt
mv text.txt text.csv
$newfile
is built using a substring removal to strip out the original extension, then concatenates the new extension. You can extend that function to handle multiple files relatively easily.
As for your ls
question, use the -d
switch. This will prevent ls
from listing the contents of directories.
Demo:
$ ls -al
total 536
drwx------ 3 owner users 528384 Jan 7 17:29 .
drwxr-xr-x 126 owner users 12288 Jan 7 17:26 ..
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f1.csv
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f2.csv
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f3.csv
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f4.csv
drwxr-xr-x 2 owner users 4096 Jan 7 17:33 test
-rw-r--r-- 1 owner users 0 Jan 7 17:27 test.csv
Wildcard rename
$ rename .csv .txt f*
$ ls -al
total 536
drwx------ 3 owner users 528384 Jan 7 17:34 .
drwxr-xr-x 126 owner users 12288 Jan 7 17:26 ..
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f1.txt
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f2.txt
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f3.txt
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f4.txt
drwxr-xr-x 2 owner users 4096 Jan 7 17:33 test
-rw-r--r-- 1 owner users 0 Jan 7 17:27 test.csv
Single-file rename
$ rename .txt .csv f1.txt
$ ls -al
total 536
drwx------ 3 owner users 528384 Jan 7 17:34 .
drwxr-xr-x 126 owner users 12288 Jan 7 17:26 ..
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f1.csv
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f2.txt
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f3.txt
-rw-r--r-- 1 owner users 0 Jan 7 17:28 f4.txt
drwxr-xr-x 2 owner users 4096 Jan 7 17:33 test
-rw-r--r-- 1 owner users 0 Jan 7 17:27 test.csv
The default ls
$ ls -l t*
-rw-r--r-- 1 owner users 0 Jan 7 17:27 test.csv
test:
total 0
-rw-r--r-- 1 owner users 0 Jan 7 17:33 dont_show_me_please
ls
that doesn't inspect directories
$ ls -ld t*
drwxr-xr-x 2 owner users 4096 Jan 7 17:33 test
-rw-r--r-- 1 owner users 0 Jan 7 17:27 test.csv
Solution 2
One thing to keep in mind when it comes to wildcards is that they're expanded by the shell. The application doesn't know whether you used wildcards or typed the names out. For example, if you type rename *.txt *.bak
, then the rename
command sees something like rename file1.txt file2.txt existingfile.bak
. That's not enough information to go on.
I'll deal with the question about ls
first, because it's simpler. If all you want is the matching names, then you don't need ls
, because the shell is already doing the expansion.
echo t*
If you want more information about the files, then pass the -d
option to ls
, to tell it not to list the contents of directories.
ls -ld t*
There is no standard utility for renaming files, because the first unix systems didn't come with one. The portable method to rename files uses a loop and is a little verbose:
for x in *.txt; do mv -- "$x" "${x%.txt}.bak"; done
There are several common utilities to rename files, none of which are guaranteed to be installed on a given unix system, but all of which are easy to install. Here are the main ones:
-
rename
from theutil-linux
suite, available on every non-embedded Linux system (and nowhere else). On Debian and derivatives (including Ubuntu), this command is calledrename.ul
. Provided that there is no occurrence of.txt
other than the final extension, you can writerename .txt .bak *.txt
-
rename
is a Perl script that Debian and derivatives ship as/usr/bin/rename
. You can rename files according to arbitrary Perl commands.rename 's/\.txt\z/\.bak/' *.txt
-
mmv
, which can rename, copy and link files according to several name-based patterns and has many options relating to what happens if a target name already exists. Note that you must use quotes to protect wildcards from expansion by the shell.mmv '*.txt' '#1.txt'
-
zmv
is a zsh function, available if and only if your shell is zsh. It can match arbitrary zsh patterns (so you can match file names according to arbitrary regular expressions, not just wildcards, and you can match files by other criteria such as dates and sizes).zmv
can also copy and link.zmv '(*).txt' '$1.txt'
If you have some control over the machines you use, my recommendation is to use zsh as your shell (it has other benefits over bash) and put these lines in your ~/.zshrc
:
autoload -U zmv
alias zmv='noglob zmv -w'
alias zcp='zmv -C'
alias zln='zmv -L'
alias zsy='zmv -Ls'
noglob
is a zsh feature that tells the shell not to expand wildcards in the argument of the command. This way you can write zmv *.txt \$1.txt
(you'll always need to protect the $
in a replacement text).
Related videos on Youtube
cwd
Updated on September 18, 2022Comments
-
cwd over 1 year
I have the misfortune of coming from a MS-DOS background - but at least it makes me appreciate how much more powerful Linux is. I've been working on getting my Linux-Fu up to par, but there are a couple things that could be done with DOS that I'm not sure how to accomplish most easily with Linux:
Renaming Multiple Files - Using Two Wildcards
c:\> dir Directory of c:\ file1.txt file2.txt file3.txt file4.txt c:\>rename *.txt *.bak c:\> dir Directory of c:\ file1.bak file2.bak file3.bak file4.bak
I know I could use
find -exec
here but it it possible to use a shorter syntax - perhapsmv
with some special flags or syntax? I guess the key to this is the second*
wildcard as linux shouldn't have a problem with the first one (i.e. i know how to select the files i want to rename using wildcards)Renaming a Single File - Using One Wildcard
c:\> dir Directory of c:\ file1.txt c:\>rename file1.txt *.bak c:\> dir Directory of c:\ file1.bak
This would be especially helpful when renaming long and unwieldy file names. I thought perhaps I could use
mv file1.txt $1.bak
to end up withfile1.txt.bak
which would also be acceptable but I'm not sure you can reference a the$1
parameter inline with a shell command. Again in this particular case it is just convenient of how ms-dos bastardizes the*
wildcard to be used as a sort of capture / replace match for part of the filename.Filtering Directory Listings with a Wildcard
c:\> dir Directory of c:\ file1.txt file2.txt file3.txt file4.txt text.txt \temp (directory) c:\> dir file* Directory of c:\ file1.txt file2.txt file3.txt file4.txt c:\> t* Directory of c:\ text.txt \temp (directory)
I'm not sure what the right syntax for doing that with
ls
is, or if it is even possible. If I did something likels t*
it will recurse into directories starting witht
. My workaround has either been usingfind . --max-depth 1 -iname "t*"
or something likels -al | grep t
- neither of which are as short and simple asdir t*
is.Finally, I know I can set up aliases to make these long commands shorter, but I'd like to learn some out-of-the-box linux-fu for doing these things because sometimes you're connected to a remote system or working on a new machine.
So how can I
mv
andls
files the same way that I used todir
andrename
files? -
cwd over 12 yearsvery nice! i think i've just moved up a belt with my linux-fu! thanks!
-
Mat over 12 yearsTip to progress a bit with this (and avoid pitfalls): use simple functions or scripts to see what happens when you do
somefunc *.Ext
if that pattern doesn't match any file, and play with quoting to see if you manage to pass a pattern around (without having it expanded) between functions. Safety tip: don't userm
anywhere while experimenting with globbing/shell expansion :-)