Hidden features of Bash
Asked Answered
D

43

72

Shell scripts are often used as glue, for automation and simple one-off tasks. What are some of your favorite "hidden" features of the Bash shell/scripting language?

  • One feature per answer
  • Give an example and short description of the feature, not just a link to documentation
  • Label the feature using bold title as the first line

See also:

Deppy answered 17/10, 2008 at 8:14 Comment(0)
S
69

insert preceding line's final parameter

alt-. the most useful key combination ever, try it and see, for some reason no one knows about this one.

press it again and again to select older last parameters.

great when you want to do something else to something you used just a moment ago.

Shirlshirlee answered 17/10, 2008 at 8:14 Comment(9)
Definetly +1. Thanks for this one, so useful yet so hidden.Elite
Can I use Alt+. to give you +2 ?Incrassate
I'm a frequent user of !$, but this is far more immediate and useful.Aramaic
I find !$ too hard to type quickly. I always have to slow down and think about putting the dollar sign second. Alt+. is faster and easier. Not to mention, you actually get to see the text before you execute it.Waggery
also you can switch through the parameters with alt+-[0-9] and then alt+.Ake
Seeing the parameter before you screw up and delete everything is definitely worth it.Foreknow
This and the other magic key combinations in subsequent answers must be features of the readline emacs bindings for bash. I use the vi bindings and they don't have any effect at all.Tjader
There's a nifty equivalent to this. UP-ARROW (and DOWN-ARROW for reverse direction). Unless I'm missing something (cygwin here), I'd say no to this and just use the arrow keys instead.Ruhl
@trinithis - this just selects the last argument, not the whole last command as UP does.Shirlshirlee
T
40

If you want to keep a process running after you log out:

disown -h <pid>

is a useful bash built-in. Unlike nohup, you can run disown on an already-running process.

First, stop your job with control-Z, get the pid from ps (or use echo $!), use bg to send it to the background, then use disown with the -h flag.

Don't forget to background your job or it will be killed when you logout.

Thoroughfare answered 17/10, 2008 at 8:14 Comment(4)
That is sweet! So many times I've wanted to do that. Can you also redirect outputs afterwards?Granulation
Better get the PID from jobs -l (or -p)Stiles
Eh - I once wrote a C program to essentially fork() and exec() its arguments, essentially a daemonizer. I can bomb the shell and run something in one go bgexec google-chrome && exit.Avra
Doesn't this does the same functionality of screen ?Horsepower
U
38

Almost everything listed under EXPANSION section in the manual

In particular, parameter expansion:

$ I=foobar
$ echo ${I/oo/aa} #replacement
faabar
$ echo ${I:1:2}   #substring
oo
$ echo ${I%bar}   #trailing substitution
foo
$ echo ${I#foo}   #leading substitution
bar
Utu answered 17/10, 2008 at 8:14 Comment(1)
Nice, so thats how I got %Ix=y% in cmd.exe ... :)Triploid
P
35

My favorite:

sudo !!

Rerun the previous command with sudo.

Parra answered 17/10, 2008 at 8:14 Comment(2)
This is a special case of https://mcmap.net/q/48192/-hidden-features-of-bash/…Utu
It's almost like saying DO IT!!Waggery
I
27

More magic key combinations:

  • Ctrl + r begins a “reverse incremental search” through your command history. As you continue to type, it retrieves the most recent command that contains all the text you enter.

  • Tab completes the word you've typed so far if it's unambiguous.

  • Tab Tab lists all completions for the word you've typed so far.

  • Alt + * inserts all possible completions, which is particularly helpful, say, if you've just entered a potentially destructive command with wildcards:

    rm -r source/d*.c Alt + *
    rm -r source/delete_me.c source/do_not_delete_me.c

  • Ctrl + Alt + e performs alias, history, and shell expansion on the current line. In other words, the current line is redisplayed as it will be processed by the shell:

    ls $HOME/tmp Ctrl Alt + e
    ls -N --color=tty -T 0 /home/cramey

Incrassate answered 17/10, 2008 at 8:14 Comment(2)
+1 for alt+* (or alt-shift-8), which lets you see the mistake you're about to makeForeknow
I turned on show-all-if-ambiguous to keep from hitting Tab twice.Tantivy
C
24

Get back history commands and arguments

It's possible to selectively access previous commands and arguments using the ! operator. It's very useful when you are working with long paths.

You can check your last commands with history.

You can use previous commands with !<n> being n the index of the command in history, negative numbers count backwards from the last command in history.

ls -l foo bar
touch foo bar
!-2

You can use previous arguments with !:<n>, zero is the command, >= 1 are the arguments.

ls -l foo
touch !:2
cp !:1 bar

And you can combine both with !<n>:<m>

touch foo bar
ls -l !:1 !:2
rm !-2:1 !-2:2
!-2

You can also use argument ranges !<n>:<x>-<y>

touch boo far
ls -l !:1-2

Other ! special modifiers are:

  • * for all the arguments

    ls -l foo bar
    ls !*
    
  • ^ for the first argument (!:1 == !^)

  • $ for the last argument

    ls -l foo bar
    cat !$ > /dev/null
    
Columella answered 17/10, 2008 at 8:14 Comment(3)
The ^R keyboard shortcut is really handy tooShannan
I also like alt-^ (alt-shift-6 on US keyboards). It expands history sequences like !:2 so you can see what a command is going to do before you run it.Joselyn
Instead of $! try typing ESC+. The last argument will appear right under your cursor.Lorenzalorenzana
A
20

I like the -x feature, allowing to see what's going on in your script.

bash -x script.sh 
Akiko answered 17/10, 2008 at 8:14 Comment(0)
A
19

SECONDS=0; sleep 5 ; echo "that took approximately $SECONDS seconds"

SECONDS

Each time this parameter is referenced, the number of seconds since shell invocation is returned. If a value is assigned to SECONDS, the value returned upon subsequent references is the number of seconds since the assignment plus the value assigned. If SECONDS is unset, it loses its special properties, even if it is subsequently reset.

Astonied answered 17/10, 2008 at 8:14 Comment(0)
M
17

Here is one of my favorites. This sets tab completion to not be case sensitive. It's really great for quickly typing directory paths, especially on a Mac where the file system is not case sensitive by default. I put this in .inputrc in my home folder.

set completion-ignore-case on
Mandal answered 17/10, 2008 at 8:14 Comment(1)
I didn't know about this, thanks. This was in my ~/.inputrc already just commented out. I turned this on and show-all-if-ambiguous.Tantivy
U
16

The special variable random:

if [[ $(($RANDOM % 6)) = 0 ]]
    then echo "BANG"
else
    echo "Try again"
fi   
Utu answered 17/10, 2008 at 8:14 Comment(2)
# [ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo “You live” :)Unfix
No need for $ sigil inside arithmetic evaluation; no need for separate evaluation and test: if (( RANDOM % 6 == 0 )); then echo "BANG"; else echo "Try again"; fi Or even shorter: (( RANDOM % 6 )) && echo "Try again" || echo "BANG".Luciferin
S
13

Ctrlx Ctrle

This will load the current command into the editor defined in the variable VISUAL. This is really useful for long commands like some of those listed here.

To use vi as your editor:

export VISUAL=vi
Salaam answered 17/10, 2008 at 8:14 Comment(1)
set -o vi then Esc on a command goes to inline editing, a plain 'v' pulls the command into a full vi editor.Orthohydrogen
A
13

Quick & Dirty correction of typos (especially useful for long commands over slow connections where using the command history and scrolling through it would be horrible):

$ cat /proc/cupinfo
cat: /proc/cupinfo: No such file or directory
$ ^cup^cpu

Also try !:s/old/new which substitutes old with new in the previous command once.

If you want to substitute many occurrences you can do a global substitution with !:gs/old/new.

You can use the gs and s commands with any history event, e.g.

!-2:s/old/new

To substitute old with new (once) in the second to last command.

Abutilon answered 17/10, 2008 at 8:14 Comment(2)
is there any way to find more about this or similar features? googling for ^foo^bar is not that satisfying :)Fain
Event Designators and Modifiers in man bash. Try !:s/old/new which substitutes old with new in the previous command once. If you want to substitute many occurrences you can do a global substitution with !:gs/old/new. This may be combined with James' post (https://mcmap.net/q/48192/-hidden-features-of-bash/…), e.g.: !$:s/old/new (substitutes old with new in the last argument of the previous command), !-2:0:gs/a/s !-2* (substitutes every occurrence of a with s in the penultimate command name and adds all the arguments of the penultimate command). Good luck!Bradleigh
S
13

Regular expression handling

Recent bash releases feature regular expression matching, so you can do:

if [[ "mystring" =~ REGEX ]] ; then  
    echo match
fi

where REGEX is a raw regular expression in the format described by man re_format.

Matches from any bracketed parts are stored in the BASH_REMATCH array, starting at element 1 (element 0 is the matched string in its entirety), so you can use this to do regex-powered parsing too.

Shewmaker answered 17/10, 2008 at 8:14 Comment(1)
It feels kinda weird not having to enclose the regex in quotes . . .Waggery
P
10

Here two of my favorites:

To check the syntax w/o really executing the script use:

bash -n script.sh

Go back to the last directory (yes I know pushd and popd, but this is quicker)

cd -
Ptyalism answered 17/10, 2008 at 8:14 Comment(1)
"cd -" has the advantage of working if you forgot to push a directory onto the stack but still want to go back there.Speos
I
8

Magic key combinations from the bash man pages:

  • Ctrl + a and Ctrl + e move the cursor to the beginning and end of the current line, respectively.

  • Ctrl + t and Alt + t transpose the character and word before the cursor with the current one, then move the cursor forward.

  • Alt + u and Alt + l convert the current word (from the cursor to the end) to uppercase and lowercase.

    Hint: Press Alt + followed by either of these commands to convert the beginning of the current word.


Bonus man tips:

  • While viewing man pages, use / to search for text within the pages. Use n to jump ahead to the next match or N for the previous match.

  • Speed your search for a particular command or sub-section within the man pages by taking advantage of their formatting:

    o Instead of typing /history expansion to find that section, try /^history, using the caret (^) to find only lines that begin with "history."

    o Try /   read, with a few leading spaces, to search for that builtin command. Builtins are always indented in the man pages.

Incrassate answered 17/10, 2008 at 8:14 Comment(0)
B
8

Running a command before displaying the bash prompt

Set a command in the "PROMPT_COMMAND" env variable and it will be run automatically before each prompt. Example:

[lsc@home]$ export PROMPT_COMMAND="date"
Fri Jun  5 15:19:18 BST 2009
[lsc@home]$ ls
file_a  file_b  file_c
Fri Jun  5 15:19:19 BST 2009
[lsc@home]$ ls

For the next april fools, add "export PROMPT_COMMAND=cd" to someone's .bashrc then sit back and watch the confusion unfold.

Befuddle answered 17/10, 2008 at 8:14 Comment(0)
P
8

Arrays:

#!/bin/bash

array[0]="a string"
array[1]="a string with spaces and \"quotation\" marks in it"
array[2]="a string with spaces, \"quotation marks\" and (parenthesis) in it"

echo "There are ${#array[*]} elements in the array."
for n in "${array[@]}"; do
    echo "element = >>${n}<<"
done

More details on arrays (and other advanced bash scripting stuff) can be found in the Advanced Bash-Scripting Guide.

Peroration answered 17/10, 2008 at 8:14 Comment(0)
T
8

Using Infix Boolean Operators

Consider the simple if:

if [ 2 -lt 3 ]
    then echo "Numbers are still good!"
fi

That -lt looks kinda ugly. Not very modern. If you use double brackets around your boolean expression you can the normal boolean operators!

if [[ 2 < 3 ]]
    then echo "Numbers are still good!"
fi
Trumpery answered 17/10, 2008 at 8:14 Comment(6)
This is not a feature of Bash, but an external program: yes, '[[' is a stand-alone program.Rohrer
madmath: I think you'll find that [ is usually a symlink or hardlink to test, while [[ is a shell built-in. It needs to be parsed by the shell otherwise < looks like input redirection.Vrablik
No, '[' is a stand alone program. '[[' is notUtu
$ type [[ [[ is a shell keyword $ which [[ $ # No outputRalphralston
Apparently SO doesn't like newlines in comments, hopefully that isn't too hard to parse. That was on Ubuntu with Bash 3.2.39, BTW.Ralphralston
[[ is a shell builtin (the < wouldn't work with a real command as it would be interpreted as input redirection). [/test is an external program, but overwritten in bash by it's own builtin.Stiles
I
7

You can ignore certain files while tab completing by setting th FIGNORE variable.

For example, if you have a subverion repo and you want to navigate more easily do

export FIGNORE=".svn"

now you can cd without being blocked by .svn directories.

Interlocutory answered 17/10, 2008 at 8:14 Comment(0)
A
7

Undo

C-S-- Control Shift Minus Undo-es typing actions.

Kill / Yank

Any delete operation C-w (delete previous word), C-k (delete to end of line), C-u (delete to start of line) etc... copies it's deleted text to the kill ring, you can paste the last kill with: C-y and cycle through (and paste from) the ring of deleted items with Alt-y

Abutilon answered 17/10, 2008 at 8:14 Comment(0)
M
7

export TMOUT=$((15*60))

Terminate bash after 15 minutes of idle time, set to 0 to disable. I usually put this to ~/.bashrc on my root accounts. It's handy when administrating your boxes and you may forget to logout before walking away from the terminal.

Massengale answered 17/10, 2008 at 8:14 Comment(0)
D
5

Brace expansion

Standard expansion with {x,y,z}:

$ echo foo{bar,baz,blam}
foobar foobaz fooblam
$ cp program.py{,.bak}  # very useful with cp and mv

Sequence expansion with {x..y}:

$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
$ echo {a..f}{0..3}
a0 a1 a2 a3 b0 b1 b2 b3 c0 c1 c2 c3 d0 d1 d2 d3 e0 e1 e2 e3 f0 f1 f2 f3
Dice answered 17/10, 2008 at 8:14 Comment(0)
U
5

Using arithmetic:

if [[ $((2+1)) = $((1+2)) ]]
    then echo "still ok"
fi
Utu answered 17/10, 2008 at 8:14 Comment(2)
It's amazing how many people don't know this, and use expr in their scripts.Shannan
Sometimes the arithmetic expansion is sufficient: ((2 + 1 == 1 + 2))&&echo OKBustup
D
4

Braces in lieu of do and done in for loop

For loop body are usually in do...done (just an example):

for f in *;
do
    ls "$f";
done

But we can use a C style using braces:

for f in *; {
    ls "$f";
}

I think this looks better than do...doneand I prefer this one. I have not yet found this in any Bash documentation, so this is really a hidden feature.

Denunciation answered 17/10, 2008 at 8:14 Comment(0)
A
4

Another small one: Alt+#

comments out the current line and moves it into the history buffer.

So when you're assembling a command line and you need to issue an interim command to e.g. find a file, you just hit alt+#, issue the other command, go up in the history, uncomment and proceed.

Ake answered 17/10, 2008 at 8:14 Comment(0)
M
4

Not really a feature but rather a direction: I found many "hidden features", secrets and various bash usefulness at commandlinefu.com. Many of the highest rated answers to this answers, I learned them on that site :)

Meerschaum answered 17/10, 2008 at 8:14 Comment(0)
T
4

Truncate content of a file (zeroing file)

> file

Specifically, this is very good for truncating log files, when the file is open by another process, which still may write to the file.

Tendance answered 17/10, 2008 at 8:14 Comment(1)
to prevent you from accidentally truncating files, set -o noclobber. Then you need to use >| file to truncate a file.Benedikt
V
4

I recently read Csh Programming Considered Harmful which contained this astounding gem:

Consider the pipeline:

A | B | C

You want to know the status of C, well, that's easy: it's in $?, or $status in csh. But if you want it from A, you're out of luck -- if you're in the csh, that is. In the Bourne shell, you can get it, although doing so is a bit tricky. Here's something I had to do where I ran dd's stderr into a grep -v pipe to get rid of the records in/out noise, but had to return the dd's exit status, not the grep's:

device=/dev/rmt8
dd_noise='^[0-9]+\+[0-9]+ records (in|out)$'
exec 3>&1
status=`((dd if=$device ibs=64k 2>&1 1>&3 3>&- 4>&-; echo $? >&4) |
    egrep -v "$dd_noise" 1>&2 3>&- 4>&-) 4>&1`
exit $status;
Vrablik answered 17/10, 2008 at 8:14 Comment(2)
What you want is to use the PIPESTATUS variable, which is an array of the exit statuses of each command in the pipe. ${PIPESTATUS[0]} would be what you want here.Godbeare
Steve, I never knew that - post it as an answer here! (I'll upvote it if you do :)Shannan
T
3

set -o vi in order to have vi-like editing of the command history as well as of the currently typed command.

Trajan answered 17/10, 2008 at 8:14 Comment(0)
M
3

One I use a lot is !$ to refer to the last word of the last command:

$ less foobar.txt
...
# I dont want that file any more
$ rm !$
Masry answered 17/10, 2008 at 8:14 Comment(2)
You can also do Alt + . for the sameAstonied
That depends on what line edit mode you have. In vi-mode (set -o vi) Alt + . does not seem to work.Masry
D
3

Easily move around between multiple directories

Not a hidden feature, but much more flexible than pushd which requires stack-like navigation.

a() { alias $1=cd\ $PWD; }

cd somewhere and type a 1. Later on just typing 1 will return to that directory.

Drive answered 17/10, 2008 at 8:14 Comment(0)
M
3

These properties are another one of my favorites.

export HISTCONTROL=erasedups
export HISTSIZE=1000

The first one makes sure bash doesn't log commands more than once, will really improves history's usefulness. The other expands the history size to 1000 from the default of 100. I actually set this to 10000 on my machines.

Mandal answered 17/10, 2008 at 8:14 Comment(0)
G
3

C style numeric expressions:

let x="RANDOM%2**8"
echo -n "$x = 0b"
for ((i=8; i>=0; i--)); do
  let n="2**i"
  if (( (x&n) == n )); then echo -n "1"
  else echo -n "0"
  fi
done
echo ""
Godbeare answered 17/10, 2008 at 8:14 Comment(0)
C
2

Here Strings (<<<). The Bash manual gives this description:

The word is expanded and supplied to the command on its standard input.

Example:

$ cat<<<"$(( 10*3+1 )) nice isn't it?"
31 nice isn't it?
Conley answered 17/10, 2008 at 8:14 Comment(0)
B
2

I have an alias r='fc-s', and I find it very useful in some limited cases. To run the last command, just type r and hit enter, and that's it. Of course, that itself is not very useful because up arrow does the same thing. But you can use r to run the previous command with substitutions. Let's say your last command was a long command compiling some file:

$ gcc -c <file_name>.c <lots of options> -o <file_name>.o

Now you want to compile another file with the same options and have a corresponding .o file:

$ r <file_name>=<new_file>

will do it. You don't have to use up arrow, navigate to the right places and then replace them each manually. This can be repeated multiple times, so you can do this next:

$ r <new_file>=<other_file>

Of course, for such a thing you have makefiles, but I hope I have shown that the alias is useful.

I haven't needed the use of this alias a lot, but there have been times that I have been glad that I have this alias!

Burner answered 17/10, 2008 at 8:14 Comment(0)
H
2

Bash has variable indirection:

$ foo=bar
$ baz=foo
$ echo ${!baz}
bar
Housley answered 17/10, 2008 at 8:14 Comment(0)
H
2

As others have mentioned, Ctrl-r is great for stepping back through your command history. But what if you want to go forward after you've taken one or a few steps too many? That's where Ctrl-s comes in handy. However, it's normally mapped to XOFF (interrupt data flow). Since that's not too useful any more because we're not using slow serial terminals, you can turn off that mapping with:

stty -ixon

in your ~/.bashrc file.

This also makes Ctrl-q available which is normally a duplicate of Ctrl-v (quoted-insert which allows you to insert a literal control character). I have Ctrl-q mapped to menu-complete which steps through completions when pressed repeatedly. I like to leave Tab set to regular complete.

You can set Ctrl-q to menu-complete by adding this line to your ~/.inputrc file:

"\C-q": menu-complete
Housley answered 17/10, 2008 at 8:14 Comment(0)
D
1

Special socket filenames: /dev/tcp/HOST/PORT and /dev/udp/HOST/PORT

Read from a daytime server (port 13):

$ cat < /dev/tcp/utcnist.colorado.edu/13

55786 11-08-13 03:34:21 50 0 0 172.3 UTC(NIST) *

This can be quite useful in conjunction with tcpserver.

A more advanced example from http://thesmithfam.org/blog/2006/05/23/bash-socket-programming-with-devtcp-2/ if you don't have access to wget or curl:

$ exec 3<>/dev/tcp/www.google.com/80 # hook up to file desc 3
$ echo -e "GET / HTTP/1.1\n\n" >&3   # send the HTTP request
$ cat <&3                            # read the HTTP response
Dice answered 17/10, 2008 at 8:14 Comment(0)
D
1

Process substitution with <(cmd ...) or >(cmd ...)

In each form, the cmd is executed with its input or output hooked up to a FIFO, and the path to that FIFO is substituted on the command line:

$ echo A file to read: <(cat), a file to write to: >(cat)
A file to read: /dev/fd/63, a file to write to: /dev/fd/62

For example, to compare two website without saving intermediate files:

$ diff <(curl -s http://tldp.org/LDP/abs/html/) <(curl -s http://www.redhat.com/mirrors/LDP/LDP/abs/html/)

If you have a command that takes a file name as input, but doesn't accept '-' to mean stdout, you can trick it:

$ do_thingee --log -
error: can't open log file: '-'
$ do_thingee --log >(cat)
do_thingee v0.2
initializing things
processing 4 things
done
Dice answered 17/10, 2008 at 8:14 Comment(0)
B
1

Using 'let' built-in bash command for basic arithmetic

A=10
let B="A * 10 + 1" # B=101
let B="B / 8"      # B=12, let does not do floating point
let B="(RANDOM % 6) + 1" # B is now a random number between 1 and 6

To do floating point evaluations, you can use the "bc" command (no part of bash).

FP=`echo "scale=4; 10 / 3" | bc` # FP="3.3333"
Befuddle answered 17/10, 2008 at 8:14 Comment(1)
There's also $(( )) for arithmetic expansion and (( )) for testsAstonied
M
0

Get more info about Key combinations in Bash in http://linuxconfig.net/manual-howto/key-combinations-in-bash.html

Maritzamariupol answered 17/10, 2008 at 8:14 Comment(0)
H
0

Quick History Search

Following gives a tcsh like history search which is handy and easier.

Add the following lines to ~/.inputrc or /etc/inputrc.

$ cat ~/.inputrc
"\e[A": history-search-backward
"\e[B": history-search-forward

You may want to use a less accidental key combination such as Esc + p. If that's the case, use

"\ep": history-search-backward
"\en": history-search-forward

Then, simply type the first few letters and press UpArrow key. It'll show the most recent command that start with the given letters.

ex:

type grep, UpArrow. It'll show something like grep -ri myText .

Horsepower answered 17/10, 2008 at 8:14 Comment(0)
J
0

Embedded Command substitution:

hostname && dig +short $(hostname) && dig +short -x $(dig +short $(hostname))

This command is good for checking RDNS on your mail server. :P

Jotting answered 17/10, 2008 at 8:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.