Linux – How to Use MS-DOS Style Wildcards with ls and mv

command linelinuxlsrename

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 – perhaps mv 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 with file1.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 like ls t* it will recurse into directories starting with t. My workaround has either been using find . --max-depth 1 -iname "t*" or something like ls -al | grep t – neither of which are as short and simple as dir 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 and ls files the same way that I used to dir and rename files?

Best Answer

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
Related Question