Living on the Unix Command Line: The Importance of a Prompt

2014/12/06 13:05:19
Print Friendly

Over the last decade, I’ve continued to gravitate more and more toward Unix and the command line while many of my colleagues have gravitated toward GUI applications. This post is not about the pros and cons of GUI vs. CLI or how one is better than the other. Suffice it to say, I live with both, but spend a lot of time working in terminals.

Today we focus on the prompt. Many people leave the prompt set to the default for their distribution, which may be fine, but in my 20 years of Unix use, I’ve found some nice tweaks that make my life easier. I share them here because you may find them useful as well.

The default prompt for a Red Hat Enterprise Linux system looks like the following:

[smj@athena bin]$

This gives me an idea of which user I’m logged in as (smj), which server I’m logged in to (athena) and the current working directory (bin). Unfortunately, in the example above, my current working directory is actually /usr/local/bin. Based on the default prompt, I cannot tell if I’m in /usr/local/bin, /opt/bin, /usr/bin, /bin, or even /home/smj/bin. This presents a problem considering how often much of the Unix directory structure repeats itself.

Another issue I have is that I’m pretty sure I’m logged in to the bash shell, but can’t really be sure. I’ve had to endure many shells in my career, from sh to bash to csh to tcsh to ksh and some I can’t even remember because they appeared so infrequently.

So, to address these problems, I spent time trying to find a prompt that would provide enough information to be useful, while working across several platforms and shells.

My current bash prompt looks like so:

18:30:33 smj@athena:/usr/local/bin
bash $ -->

My current tcsh prompt looks like so:

18:40:01 sjone@procyon:/usr/local/bin
tcsh % -->

There is no color in either prompt. This avoids any issues between different terminal color schemes or terminal types. It works equally fine in the Linux console and hpterm.

You may be asking, why does he need all of this information in the prompt? Let’s review each part of my prompt.

Newline at start

Consider the following text from an open terminal with a prompt containing no information:

total 892
-r-sr-xr-x. 1 root bin 234404 Aug  3 18:35 cdcc
-r-xr-xr-x. 1 root bin  44568 Aug  3 18:35 dccif-test
-r-sr-xr-x. 1 root bin 627422 Aug  3 18:35 dccproc
#cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
#128.82.7.27 e-2104-13 e-2104-13.cs.odu.edu
#netstat --inet
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:5011        TIME_WAIT
tcp        0    116 athena.littleprojects.o:ssh 192.168.191.210:5012        ESTABLISHED
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:5013        TIME_WAIT
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:5014        TIME_WAIT
tcp        0      0 localhost:10024             localhost:58377             TIME_WAIT
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:di-ase      TIME_WAIT
tcp        0      0 localhost:10025             localhost:53230             TIME_WAIT
tcp        0      0 localhost:10025             localhost:56526             TIME_WAIT
tcp        0      0 localhost:10024             localhost:58375             TIME_WAIT
#gvim chicken.txt

Can you quickly glance out it and tell me which commands have been executed? I need some separator on the screen to indicate the individual command executions so I can tell what happened. Without a separator, it looks like one big garbled mess, so I opted for a newline between the prompt and the command executions.

Try it now?

total 892
-r-sr-xr-x. 1 root bin 234404 Aug  3 18:35 cdcc
-r-xr-xr-x. 1 root bin  44568 Aug  3 18:35 dccif-test
-r-sr-xr-x. 1 root bin 627422 Aug  3 18:35 dccproc

#cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
#128.82.7.27 e-2104-13 e-2104-13.cs.odu.edu

#netstat --inet
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:5011        TIME_WAIT
tcp        0    116 athena.littleprojects.o:ssh 192.168.191.210:5012        ESTABLISHED
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:5013        TIME_WAIT
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:5014        TIME_WAIT
tcp        0      0 localhost:10024             localhost:58377             TIME_WAIT
tcp        0      0 athena.littleprojects.o:ssh 192.168.191.210:di-ase      TIME_WAIT
tcp        0      0 localhost:10025             localhost:53230             TIME_WAIT
tcp        0      0 localhost:10025             localhost:56526             TIME_WAIT
tcp        0      0 localhost:10024             localhost:58375             TIME_WAIT

#gvim chicken.txt

Now I can clearly see that the top is the output of some command that has scrolled off the screen, but the cat, netstat, and gvim commands came next. It’s not that I can’t figure out what commands were executed (or even look at the history), but that when I’m comparing commands and output to each other, I need to be able to quickly see which sections of output belong to which commands. For me it’s a legibility and time-saving measure.

Time last command exited

At the beginning of the prompt is a timestamp.


18:30:33 smj@athena:/usr/local/bin
bash $ -->

This timestamp serves as a “poor man’s time command” letting me know (roughly) just how long the previous command took to run. It also serves as a way to keep track of times if I have a meeting or other engagement coming up. I put it in front, so when I see the prompts in sequence I can compare them to one another.

Username

Next is the username.


18:30:33 smj@athena:/usr/local/bin
bash $ -->

I don’t have the same username for each account. I also want to know if I’m running as root or a regular user. Sometimes that forces me to notice that I’m root before I do something dangerous. With one terminal up, this is not a problem. When I’m switching between five, it becomes increasingly important to keep each straight.

Server hostname

And equally useful to the username is the hostname of the server to which I’m connecting.


18:30:33 smj@athena:/usr/local/bin
bash $ -->

With the hostname I can tell different terminal sessions apart and ensure that I don’t make a mistake of typing the wrong command on the wrong server.

Full Path

As noted above, I like seeing the full path in my prompt. This way I can tell where I am on the server.


18:30:33 smj@athena:/usr/local/bin
bash $ -->

I also have a newline inserted after the path because a long path can scroll pretty far across the screen and some terminals have an issue including both the command and a long prompt on the same line.

The whole username@hostname:path exists so I can easily copy and past this string into an SCP command for moving files between servers. This is easier than typing scp myfile username@hostname:path by hand, especially considering hitting <tab> doesn’t work to expand the path for the server you are copying to.

Shell Name

This was one of the last things I added. I use a lot of bash; a LOT OF bash, but occasionally I have accounts (thank you ODU) that are tcsh, or I need to use an ancient server running csh or sh. I wanted to ensure that I could readily identify the shell as I switch between systems, so I have the prompt state which shell is running.


18:30:33 smj@athena:/usr/local/bin
bash $ -->

This means that I actually have login scripts for each shell that attempt to create as close as possible an approximation of the bash prompt. For example, if I’m suffering through an actual Bourne Shell session, I only get this:
sjone@E-2104-13.cs.odu.edu
sh $ -->

For tcsh, I get this:

18:40:01 sjone@procyon:/usr/local/bin
tcsh % -->

The point is that the shell is identified on the line on which I’m typing commands. This way I will know about the shell so I can change my syntax for items, such as redirecting stderr to stdout, when using a shell other than bash.

I do not control all of the servers I log into. Even though I may prefer bash, administrators on other systems have everyone default to tcsh or other shells. I try to ensure that my login prompt reflects this difference, especially when I’m working across several machines.

"The Arrow"

I added the arrow (–>) with an additional space afterwards so I can separate the prompt from the command that has been run. I find this to be useful, much like the newline between prompts, when looking at the output from a series of commands. It allows me to separate the commands I run from their output.

No color

Finally, I work across multiple systems with different terminal emulators, some of which I cannot choose for myself. At work I use PuTTY or Attachmate Reflections. At home I prefer the Mac OS X terminal program, or < a href="https://help.gnome.org/users/gnome-terminal/stable/">gnome-terminal, but I don’t always get to use my home computer for all of my projects. Sometimes, I don’t even get to configure the terminal how I would like!

Imagine that I had selected a color scheme using blue. Now imagine that same blue with a black background. Was that even legible? If cannot change the background to white or gray then I have to strain to read that text. Also, consider yellow on a white background. If I cannot reliably control my background colors, I really can’t select any meaningful foreground colors either.

Hence, I put not color into my prompt.

Setting up the prompt

To generate my prompt, I set the PS1 variable to the following value in my .bashrc file:

PS1='\n\t \u@\h:\w\nbash \$ --> '

The \n provides the newline, the \t provides the time, the \u provides the username, the \h the hostname, the \w the full path, and the rest is just text.

See the bash man page.

TCSH is not that much different:

set prompt = "\n%P %n@%m:%~\ntcsh \% --> "

Here, the %P provides the time, the %n provides the username, the %m the hostname, the %~ the full path (substituting ~ for my home directory), and the rest is just text.

Other shells do not have these fancy variables for telling time or providing other information, so for those shells I just set them with a text string providing as much meaningful information as I can get. I even have a custom Windows Command Prompt I use when I’m able to set it myself.

Summary

I hope you have found this article a useful resource. Though I realize not everyone is in the same situation I am, having to adapt to many differing environments, I hope that you can take away something useful from this discussion about the importance of a prompt on the command line.

For more information on how to set up your prompt effectively, Carla Schroder has a nice article discussing the use of colors and other features. For more in depth information on the bash prompt, check out the Giles Orr’s Bash Prompt HOWTO from the Linux documentation project. Understudy has a similar article to this which discusses more shells.

Whatever you do, take some pride in your prompt! It doesn’t just have to be that place where you give information, you can also get information.

Operation Not Permitted

2012/06/02 11:31:01
Print Friendly

I’m renting a new server from my dedicated server company, and got a really good deal.

I’ve wanted SELinux with my Red Hat/CentOS/Scientific Linux/Fedora servers since Red Hat ironed out the details around Fedora 6. The benefits are that someone breaking into one of the services is “sandboxed” and it limits the damage they can do to the system.

The drawbacks are that often items don’t function as one would expect because you have not found the correct set of draconian rules to enable the functionality you are looking for, but Red Hat has worked to iron out a lot of that to great success in the last few years.

I changed my SELinux setting in /etc/selinux/config from “disabled” to “permissive” so I can start up services and monitor /var/log/audit/audit.log to determine which SELinux rules should be altered.

After rebooting my server and analyzing the logs, I used the fixfiles command to change the SELinux contexts to get rid of some errors and saw the following message on several files:


failed to change context of `/etc/resolv.conf' to `system_u:object_r:net_conf_t:s0': Operation not permitted

I assumed (wrongly) that this must be some issue with SELinux. I tried the chcon command and got the same message. I could run chcon fine on other files, so what was the problem?

I decided maybe something was up with the file inode, so I tried moving the file aside with the intent of filling it in with the proper data/permissions/etc. after. The cp and mv commands also returned with Operation not permitted.

The permissions on the file showed that the files were owned by root and root had write permission to the file:

-rw-r--r--. 1 root root 97 May 31 17:39 resolv.conf

Now this was less an SELinux problem and more an issue specific to these files. Some more goggling led me to the obscure lsattr command, which showed:

----i---------- /etc/resolv.conf

The i is the “immutable bit”. It is the Linux equivalent to Microsoft Windows’s “Read Only” checkbox. I had known about it, but could not remember the command to check for it.

Removing the immutable bit was easy using chattr:

chattr -i /etc/resolv.conf

And then I was able to run fixfiles, chcon, mv, cp, and whatever else I wanted on the given files.

I’m guessing the admins at the server hosting company had set the bit inadvertently during installation of the system.

Using launchd to run scheduled jobs

2011/08/29 14:25:22
Print Friendly

My new server’s predecessor ran Linux. I had a few scheduled jobs, like the ones that back up this blog, its database, and so on. On Linux the tool used for scheduling jobs is cron. Lo and behold, Apple has deprecated cron on their systems in lieu of an Apple-grown tool named launched. Launchd’s job is to replace cron, init, the rc scripts, the file alteration monitor, and a whole host of other normal Unix utilities in favor of one huge behemoth process that starts at boot. I’ve got mixed feelings about this concept, as it goes against the Unix philosophy of “doing one thing and doing it well”, but I chose OSX and have tried to learn their way of doing things.

I found several articles describing how one creates the plist files used by launchd. These XML files contain the information on the process you wish to run, including when to run it, how to run it, and who is allowed to run it. Here I will detail how I used it to merely run a scheduled job every night at midnight.

To start with, I have a simple script that backs up the database on the server:

#!/bin/sh
 
BACKUPDIR=$HOME/Backups/lawrence.littleprojects.org/db
REMHOST=lawrence.littleprojects.org
OUTPUTFILE=$REMHOST.sql
 
# if we don't have a backup directory, make it
if [ ! -e $BACKUPDIR ]; then
  mkdir -p $BACKUPDIR
fi
 
cd $BACKUPDIR
 
# run the remote database dump command
ssh $REMHOST "mysqldump --verbose --user=root --password='XXXXXXXXXXXX' --all-databases" &gt; $OUTPUTFILE
 
# if the zipped output file already exists, move it before zipping the new one
if [ -f $OUTPUTFILE.bz2 ]; then
  mv $OUTPUTFILE.bz2 old-$OUTPUTFILE.bz2
fi
bzip2 $OUTPUTFILE
 
chown smj:staff $OUTPUTFILE.bz2

This script is creatively entitled backup-database. I want it to run every night so I have an exact copy of this website’s database in case the web server goes down and I have to reinstall everything.

Under cron, I would run crontab -e and then put the following line into the editor that is brought up:

0 * * * *    $HOME/bin/backup-database

Apple made this simple step a lot more complicated than, in my opinion, it needed to be; but they gave me a tool that is a lot more powerful that mere cron.

To use launchd, I needed to make a file in the directory /Library/LaunchDaemons named org.littleprojects.backup.lawrence.db.plist that contains the following XML code:

<!--?xml version="1.0" encoding="UTF-8"?-->
 
 
 
    Label
    org.littleprojects.backup.lawrence.db
    ProgramArguments
 
        /Users/smj/bin/backup-database
 
    StartCalendarInterval
 
        Hour
        0
        Minute
        0

I’m a little annoyed at how Apple handled the whole key-value syntax, but I’ll let that be. The file reads as follows: create a job named org.littleprojects.backup.lawrence.db that will run the program /Users/smj/bin/backup-database at the 0th hour and 0th minute of ever day (midnight). I’m not sure if this is any less difficult to read than the cron syntax, but it is more difficult to write to be sure.

Once I created the file, I then needed to load it into launchd, which could be done by running the following commands:

sudo launchctl load org.littleprojects.backup.lawrence.db.plist

If you do this you, do not need to reboot as others claim. This is Unix and reboots should only be necessary for major operating system changes. Rebooting in order to create a scheduled job is not only ridiculous, but time-wasting and leads to error-prone behavior like making many untested scheduled jobs at once in order to save on reboots. I always try to find a way around reboots to avoid error-prone behaviors!

So, I did the same thing for other scripts I wanted to run at certain times. I understand why apple decided to use launchd, I just wish they would have made it easier to configure. Lingon exists in the app store for the express purpose of helping you create these plist XML files, but it will not load them for you and instead recommends you reboot after each edit, which I noted my distaste for above.

In the future I will be exploring how to best use launchd for other purposes, like restarting services, and scheduling scripts to run when other system events occur.

References:

Lil’ bit: scp and echo statements in your login scripts

2011/01/16 12:26:43
Print Friendly

For years, I have used scp to transfer files between Unix/Linux machines.  I noted quite a while ago that having an echo statement in my login script (in this case .bashrc) caused the file transfer to fail, so I removed all echo statements from login scripts, using them only for temporary debugging.  Today, I believe I have found the way to have my cake (use scp to copy files) and eat it too (leave the echo statements in the login script).

If your .bashrc looks like this:

#!/bin/bash
echo "Running the .bashrc file"
 
# correct spelling mistakes
shopt -s cdspell
 
# save multiline commands in the command history
shopt -s cmdhist
 
# some useful aliases
alias c='clear'
alias ls='ls -p'
alias vi='vim'
 
echo "done with .bashrc file"

And you connect via SSH, you’ll see the following:

[me@otherhost]$ ssh myhost
me@myhosts's password:
Running the .bashrc file
done with .bashrc file
[me@myhost me]$

Which is fine for an SSH session.  In fact, you might even ask questions of logging in users, or dump an entire warning banner to the screen to indemnify you for legal reasons.  Many interactive possibilities exist.

Now say you wish to copy a file from one server to another:

[me@otherhost]$ scp myfile myhost:~/
Running the .bashrc file

Once this is done, you only see the output from the first echo.  What’s worse, the copy never happens.  Some claim this is a bug.  The fact that the bug was reported in 2000 and still exists in scp indicates to me that the scp authors don’t consider it a problem.

The solution is in testing for the existence of a terminal:

#!/bin/bash
if tty &gt; /dev/null 2&gt;&amp;1; then
    echo "Running the .bashrc file"
fi
 
# correct spelling mistakes
shopt -s cdspell
 
# save multiline commands in the command history
shopt -s cmdhist
 
# some useful aliases
alias c='clear'
alias ls='ls -p'
alias vi='vim'
 
if tty &gt; /dev/null 2&gt;&amp;1; then
    echo "done with .bashrc file"
fi

Now scp will work without any output to the screen. More importantly, your files will be copied:

[me@otherhost]$ scp myfile myhost:~/
me@myhost's password:
myfile                               100% 1896KB   1.9MB/s   00:00

Thanks to the forums on Ars Technica for leading me to the answer to this issue. As that post existed in 2000, I now see why the openssh folks haven’t “fixed” scp.

A memo on Open Source

2010/06/10 03:34:13
Print Friendly

In light of the arrival of the iPhone 4, I’ve been confronted with an age-old question:  what is the current state of open source?

Apple is ruling the future of the phone market with iPhone.  Android is the up-and-coming competitor.  Google is betting on their Linux phone, and betting hard.  Google’s goal is to make money with their ads.  I figured it would work, until I saw that Apple had iAds, something far more media-rich than Google Ads.

Apple is creating the tablet market with the iPad.  Some manufacturers have been working on Android tablets, but they will be late to the game.

Apple is moving into the mobile gaming market with the iPhone/iPod/iPad.  Android hasn’t really started to achieve the level of choice available in the Apple App Store.

Microsoft is continuing to hold onto its desktop OS market.  OS X is making inroads into this market because of Microsoft’s failure to get wide adoption of Windows Vista.  It remains to be seen if Windows 7 can recoup those losses.  Desktop Linux failed on many fronts for many reasons.  OS X and Windows have something Linux lacked, a consistent interface for applications.  Ubuntu is the most promising Desktop Linux available for the average user, but it can’t overcome the inconsistency across the UIs of the thousands of applications it supports.

Microsoft is continuing the hold onto its business server market.  This is the market of file sharing and directory services.  Neither Apple nor the Open Source world have been able to offer an alternative in these areas that has the level of adoption as Windows Servers.

Linux seems to be best suited for appliances, like wireless routers, but it remains to be seen how many hardware manufacturers see it as beneficial to continue to use Linux rather than implementing their own OS and utilities.

Linux seems to be the platform of choice for hosting application servers, like JBoss.  Unfortunately for Linux, most (all?) of these application servers can also be easily run on Windows or OS X.

Linux seems to be the platform of choice for web servers.  This is largely because Linux is inexpensive and IIS is not as feature-rich as Apache.  Apache can be easily run on Windows or OS X.

So, where does Open Source fit into this new world order?

There are several options for the Open Source developer (not in any particular order):

  1. Write applications in Java, Scala, or some other language that is platform independent, in hopes that it will be available on the largest number of platforms.  This will not help you on iOS, where you are stuck with Apple’s API and Apple’s implementation of Objective-C.
  2. Continue to develop applications for the LAMP platform.  As most of the these apps only need the AMP without the Linux, get used to the idea that folks might run it on Windows or OS X.
  3. Write some libraries that can be incorporated into iOS apps.  This may violate Apple’s terms of use, so be careful.
  4. Continue to write desktop applications that only run on Linux.
  5. Android.  Google is actually achieving some consistency for apps on its Linux platform, but not to the degree that Apple’s draconian tactics have achieved.
  6. Make something NEW.  Actually innovate in a way that forces the Apples and the Microsofts of the world to fear, and, eventually copy, the idea/concept/software.  This is an area where open source once shined.