Saturday, June 21, 2008

Dual pane Linux file managers: mc and emelfm

I have been computing all my life without using a dual pane file manager until one day I decided that having one will greatly enhance my qualify of life. What I had in mind was something that will make my life easier in the copying or moving of files from 1 directory to another.

By dual pane (or twin-pane) file manager, I mean a file manager that displays two directories side by side (one active, one passive). The term is confusing because often a dual pane file manager has a third pane that lets you enter commands to execute on the active directory.

Staying true to my preference for Command Line Interface (CLI), I first tried Midnight Commander (mc). As advertised, mc is a file manager that is loaded with features. What I am looking for though is modest: a file manager with 2 directory panes that will satisfy my very basic day-to-day file management needs.


I found myself customizing the look and feel of mc in the early adoption stage. The nostalgic white text on blue background had to go.

You can run mc with the -b option to force black and white.
$ mc -b

If you don't want to go that far, you can customize the color used by editing the init file (~/.mc/ini).

I inserted the following lines into the ~/.mc/ini file.

[Colors]
base_color=lightgray,green:normal=green,default:selected=white,gray:marked=yellow,default:markselect=yellow,gray:directory=blue,default:executable=brightgreen,default:link=cyan,default:device=brightmagenta,default:special=lightgray,default:errors=red,default:reverse=green,default:gauge=green,default:input=white,gray:dnormal=green,gray:dfocus=brightgreen,gray:dhotnormal=cyan,gray:dhotfocus=brightcyan,gray:menu=green,default:menuhot=cyan,default:menusel=green,gray:menuhotsel=cyan,default:helpnormal=cyan,default:editnormal=green,default:editbold=blue,default:editmarked=gray,blue:stalelink=red,default


I also rearranged the panes so that they are placed horizontally (instead of vertically). You can interactively customize the placement using the mc Options menu (when you are already in mc). Alternatively, you can edit the ~/.mc/ini file, and make sure that the following horizontal_split line appears in the [Midnight-Commander] section:
[Midnight-Commander]
....
horizontal_split=1


My mc window looked like this after the above customization.


Another major annoyance is that if you run mc in a Gnome window, you cannot use the F10 key to quit mc. It turns out that F10 is normally the key to access the menu-bar in Gnome. To quit mc, you use your mouse to click the Quit soft-key.

If it bothers you as much as it bothered me, you might want to disable the standard F10 menu-bar shortcut key for Gnome. To do that:
$ gconftool-2 --set /apps/gnome-terminal/global/use_menu_accelerators --type bool false

After giving it a good run for about 3 weeks, I gave up on mc. The determining factor was how mc handles the Tab key.

Like other dual pane file managers, mc has a third pane that acts as the command line input window. However, the type of commands that will run in that window seems very limited. I tried commands such as date, pwd, ls, but they all showed no output. The most annoying feature to me is the Tab key. I am used to tapping the Tab key in a bash window for command completion. If you hit Tab while you are typing away in the mc's command line window, the effect is that mc switches the active focus to the other pane.

I won't be surprised if someone tells me there is a way to customize the handling of the Tab key. But, at that point in time, I concluded that I will look for some other file manager that requires less adjustment and customization.

I first became aware of emelfm because it is packaged with the Damn Small Linux (DSL) distribution. I decided to take a look at it because if it is included in DSL, it should be simple enough to satisfy my modest needs.

emelfm is a GUI-based double pane file manager. It has a clean, simple, and intuitive UI. It does the basic file copying and moving. It also has some nice additional features such as the ability to set bookmarks and apply filters to filenames.

One minor annoyance I find is when I try to copy a file in the active window to the same location. Essentially, what I want to do is copy and paste a file (to a new name) in the same directory in the active pane. I cannot use the Copy button to do it because it will only copy the file to the other pane. Instead, I need to enter the cp command explicitly in emelfm's command line window to do the copy.

[P. Evans pointed out that emelfm has a Clone plugin that copies a file to the same directory. Click Configure and then Buttons. Add a button and select the Clone plugin.]

Using the command line window in emelfm turns out to be a pleasant experience (as compared to mc). Hitting the Tab key will only switch the active pane if your mouse cursor is not in the command line window. If the cursor is in the command line window, hitting the Tab key does what I expect: command and filename completion. The commands that did not work in mc all worked well in emelfm (date, pwd, ls).

I have been happily using emelfm since then. There are other GUI-based dual pane file managers out there: e.g., Krusader, Double Commander. I have not tried them. However, I do most file management on the CLI, and only occasionally use a GUI-based file manager to do file copying. As such, emelfm satisfies my requirement for a GUI-based file manager.

Friday, June 20, 2008

Run emacs in batch mode to byte-compile elisp files

emacs is my favorite text editor. (No flame please.)

Little known and used perhaps is the fact that emacs does run in batch mode. By batch, I mean emacs accepts and executes commands in the command line, without any user interaction.

You can emulate a typical emacs text editing session as follow:
$ emacs -batch afile.txt -l runme.el -f save-buffer -kill

This command opens the afile.txt in batch emacs mode.

The -l parameter specifies an elisp file to load and execute. In this example, it loads the elisp file named runme.el which modifies the file buffer.

Next, it saves the buffer. Note that the -f parameter tells emacs to run a command, save-buffer in this case.

Finally, the -kill tells emacs to exit.

Below, I run emacs in batch to byte-compile a list of source elisp files. This comes in handy if you have a lot of source elisp files to compile.

$ emacs -batch -l runme.el -kill

Note that I do not need to save the buffer. Hence, no save-buffer.

The output byte-code files (.elc) will be put in the same directory as the source elisp files.

The runme.el file contains the commands to byte-compile the source elisp files.
$ cat runme.el
(byte-compile-file "/home/peter/emacs/mods/log4j-mode.el")
(byte-compile-file "/home/peter/emacs/mods/vm-w3m.el")

Wednesday, June 18, 2008

Create File of a Given Size ... with random contents

A while back, I wrote about how to create a zero-filled file of any arbitrary size. This is part 2 where I share how to create a file of random contents (not just zeroes).

Recently, I ran into a situation where a zero-filled file is insufficient. I needed to create a log file of size 2 MB in order to be zipped up and copied to another server.

To create the 2MB file (with all zeroes), I run the dd command:
$ dd if=/dev/zero of=a.log bs=1M count=2

I quickly realized that the test result would be invalid because zipping a all-zero file dramatically reduced its size.

$ gzip a.log
$ ls -hl a.log*
-rw-r--r-- 1 peter peter 2.1K 2008-06-14 14:36 a.log.gz
$


I decided to create a 2 MB file of random contents instead. This is how.

$ dd if=/dev/urandom of=a.log bs=1M count=2
2+0 records in
2+0 records out
2097152 bytes (2.1 MB) copied, 1.00043 seconds, 2.1 MB/s
$ gzip a.log
$ ls -hl a.log*
-rw-r--r-- 1 peter peter 2.1M 2008-06-14 14:43 a.log.gz
$


To take a look at the random contents of a.log, use the hexdump command:

$ hexdump a.log |head
0000000 c909 2da7 4a77 22fc 88b6 b394 be42 b0c1
0000010 1531 f9d5 4b3d 390d e670 da2c e7e9 b681
0000020 0518 2b5d 5a66 ef76 c297 7f73 2d0b 453e
0000030 ba47 c268 26f9 79b5 1816 82ac 2e76 0ff2
0000040 c1e8 e14f 898f 2507 9c29 83b7 226c 0d65
0000050 f3f6 6eb4 62d9 410b b566 c522 ffca fbac
0000060 81f6 d91c dd34 18cd f873 8073 fa02 20c1
0000070 06bb 7e32 dc2e 13b2 a345 aadd 8700 fa9e
0000080 e28e 1b58 c25f 4619 c8bc 8110 6306 a2fc
0000090 9766 d98f 648e cec7 d654 2eaa 1f6f 839f

Monday, June 16, 2008

Smart case-insensitive, incremental search using vim

My previous article describes my top annoyance with the vim text editor, namely, its syntax highlighting. In this article, I will tell on a close second annoyance, and what to do about it.

vim, by default, searches case sensitively. If you search for apple, you will find exactly that, but not Apple or APPLE.

In most situations, I want my searches to be case insensitive. To make search case sensitive, set the corresponding vim option by typing :set ignorecase (and press the return key).

ignorecase has a shorter alias called ic. You can type :set ic and it will have the same effect.

Now searching for apple will give you Apple, APPLE as well as apple.

But, what about the situations where you DO want case-sensitive searching?

You can always disable the ignorecase search, by typing the following and hit return:
:set noignorecase

Flipping between ignorecase and ignorecase can be tiresome for even the most patient. Luckily, vim has the smartcase option that you can use TOGETHER with ignorecase.

Type the following:
:set ignorecase (and hit return)
:set smartcase (and hit return)


With both ignorecase and smartcase turned on, a search is case-insensitive if you enter the search string in ALL lower case. For example, searching for apple will find Apple and APPLE.

However, if your search string has one or more characters in upper case, it will assume that you want a case-sensitive search. So, searching for Apple will only give you Apple but not apple or APPLE. It turns out to be quite satisfactory for most people (including yours truly).

While we are on the topic of vim search options, there is a third option that you should know:
:set incsearch (and hit return)

incsearch stands for incremental search. It means that you will see what vim matches as you type in each letter of your search string (without having to hit return before search is even attempted).

For example, you type / to initiate search, and right after you type the letter a, vim will highlight the a in apple. As you type the next letter p, vim will highlight ap in the word apple.

You can often find what you are looking for before you finish typing in the entire search string. It is also helpful if you are not quite sure of what you are searching for, and depending on the instant feedback as you type, you can make corrections to the search string by backspacing.

If you want to enable those options permanently, insert the following lines into your ~/.vimrc file.
set ignorecase
set smartcase
set incsearch


Happy searching!

Saturday, June 7, 2008

How to find a file and cd to its dirname using command substitution

Many times I know the name of a file on my Linux machine, say unknown.txt, but I don't know the directory the file is in.

If I want to change directory to the directory folding the file, I used to do a 2-step process:
$ find / -name unknownfile.txt 2>/dev/null
/home/peter/status/2007/november/unknownfile.txt


Note: if you know more about where that file may be, you can always make the starting point of the find more specific (say "/home/peter" instead of just "/").

Then, I manually enter the cd command with the path discovered from the last step.
$ cd /home/peter/status/2007/november
$


I got tired of re-entering the directory name in this 2 step process. So, I set out to see if I can automate it further.

Here we go.

There may be more than 1 file of that file name, and if so, let's take the first one.

$ find / -name unknownfile.txt  2>/dev/null | head -n 1
/home/peter/status/2007/november/unknownfile.txt



We need to extract just the directory path, and leave out the filename (so that we can cd to the directory).

The dirname command should be able to do that, but alas, dirname cannot take its input from STDIN. dirname expects the file name as a command line parameter. The following will fail.
$ find / -name unknownfile.txt  2>/dev/null | head -n 1  | dirname
dirname: missing operand
Try `dirname --help' for more information.


This is where command substitution comes in.

The final command is:
$ cd $(dirname $(find / -name unknownfile.txt  2>/dev/null | head -n 1))
$ pwd
/home/peter/status/2007/november
$


Let's break it down. Using command substitution, the output of one command becomes the input parameter to another command.

The first command substitution we use is:
dirname $(find / -name unknownfile.txt  2>/dev/null | head -n 1)


In this case, the output of "find / -name unknownfile.txt 2>/dev/null | head -n 1" becomes the input to dirname.

Then, we again use command substitution to make available the output of dirname as the input to cd.

cd $(dirname $(find / -name unknownfile.txt  2>/dev/null | head -n 1))

Thursday, June 5, 2008

Show progress during dd copy

(2016-01-09 Update)
I owe RapidElectronic for his excellent comment regarding the new dd command. Beginning with version 8.24, you can specify the parameter, status=progress, to the dd command. By using this new parameter, you no longer need to send an explicit USR1 signal to the dd process to request an update of the disk copy statistics; it will automatically print periodic updates in the standard output.


$ sudo df if=/dev/sda of=/dev/sdb status=progress

Note that sending the USR1 signal will continue to work for the new dd.

(Original article)

dd is a popular, generic command-line tool for copying files from 1 location to another. It is often used to copy entire disk images.

Like many Linux command line tools, it operates silently unless something unexpected happens. Its lack of visual progress feedback is a nice feature for scripting. However, it can leave you wondering about its progress if you are interactively dd-copying a large disk.

To illustrate, you run the following (valid, but perhaps not very useful) dd copy:

$ dd if=/dev/random of=/dev/null bs=1K count=100 

It will run for a few minutes as it copies (and immediately discards) 100 blocks of randomly generated data, each of size 1 KB.

To get a progress report while dd is running, you need to open another virtual terminal, and then send a special USR1 signal to the dd process.

First, find out the process id of the dd process by running the following in the new virtual terminal.

$ pgrep -l '^dd$'
8789 dd
$

To send the USR1 signal to the dd prcoess:

$ kill -USR1  8789
$

Note that as soon as the USR1 signal is detected, dd will print out the current statistics to its STDERR.

$ dd if=/dev/random of=/dev/null bs=1K count=100
0+14 records in
0+14 records out
204 bytes (204 B) copied, 24.92 seconds, 0.0 kB/s

After reporting the status, dd will resume copying. You can repeat the above kill command any time you want to see the interim statistics. Alternatively, you can use the watch command to execute kill at a set interval.

$ watch -n 10 kill -USR1 8789

P.S.

Other articles from this blog on the dd command:
Create files of a given size

Tuesday, June 3, 2008

How to number each line in a text file on Linux

Some Linux commands support options that will number the input lines as a side effect, e.g., grep, and cat. The nl command is on the other hand dedicated to the task of numbering lines in a text file. If you want maximum flexibility, sed or perl is your best bet.

Suppose you want to number the lines in the input.txt file which contains:
123

456

789


abc

def

ghi


Below are some ways to number input.txt:

  • cat -n
    $ cat -n input.txt
    1 123
    2
    3 456
    4
    5 789
    6
    7
    8 abc
    9
    10 def
    11
    12 ghi


  • grep -n
    $ grep -n '^' input.txt
    1:123
    2:
    3:456
    4:
    5:789
    6:
    7:
    8:abc
    9:
    10:def
    11:
    12:ghi



  • nl

nl inserts the line number at the beginning of each non-empty line. By default, the line number is 6 characters wide, right justified with leading spaces. A tab is inserted by default after the line number.
$ nl input.txt
1 123

2 456

3 789


4 abc

5 def

6 ghi


If your file is small, 6 is perhaps too wide for the line number field. To adjust the width of the line number, use the -w option. To make nl number all lines including blank ones, add -ba option.
$ nl -ba -w 3 input.txt
1 123
2
3 456
4
5 789
6
7
8 abc
9
10 def
11
12 ghi


If you don't want a tab after the line number, you can replace the tab with null (no separator between line number and rest of line), or any string you want. Use -s '' for no separator or -s ' ' for a space.
$ nl -ba -w 3  -s ' ' input.txt
1 123
2
3 456
4
5 789
6
7
8 abc
9
10 def
11
12 ghi


If you prefer left justifying the line numbers, set the field width to 1 (or use -n ln option).

$ nl -ba -w 1  input.txt
1 123
2
3 456
4
5 789
6
7
8 abc
9
10 def
11
12 ghi



nl is flexible enough to only number lines that match a regular expression. This is done by specifying the option -b p followed by a regular expression on the command line.

Number only those lines that start with either the character 1 or 4.

$ nl -b 'p^[14]' -w 3 -s ' ' input.txt
1 123

2 456

789


abc

def

ghi


Note that we specify -s ' ' to use a single space as the separator between line number and body text. This is used to preserve text alignment (the default tab will cause output to look messy).

Number only those lines that contain the "words" 12 or ef.
$ nl -b 'p12\|ef' -w 3 -s ' ' input.txt
1 123

456

789


abc

2 def

ghi