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 the util-linux suite, available on every non-embedded Linux system (and nowhere else). On Debian and derivatives (including Ubuntu), this command is called rename.ul. Provided that there is no occurrence of .txt other than the final extension, you can write

    rename .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).