How can I use ms-dos style wildcards with ls and mv?
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
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).