Bash is a standard shell on Linux and with version 3.2 or later available on
all enterprise platform and installed on Solaris and AIX it make sense to make it
standard interactive shell. The current version as of June 2017 is 4.2. Only HP-UX does not include bash by-default, but HP
does provide a depot package for bash 4.00. Bash 3.2 and later has important
enhancements that make it a better shell (see
Annotated List of Bash
Enhancements) although scripts should be generally limited to small to medium
size as Perl represents much better scripting environment then bash and is installed
by default on all platforms including HP-UX and AIX.
Bash can be called an accidental scripting language. If started very modestly more then 40 years
ago with Borne shell (1977) and C shell (1978). And then gradually had grown into a
scripting
language. the first major enhancement were done in ksh88 and then ksh93, Then bash 30, and 4,0
introduced additional one (but generally in the framework of ksh93).
The key problem with bash is that it is "rarely used language" for most users. That means that
while users might use bash as a command interpreter daily, they program more or less complex
scripts rarely (say once a quarter on average). And from one time to another manage to
forget most of the important staff related to this language. That makes programming in bash a real
challenge.
Also your programming skills evaporate in several months or so, unless you carefully document your "achievements".
You will forget them and go down again to the most basic level of usage of this language when you need to write another bash script.
And you might repeat the same mistakes or even blunders again and again. Logbooks is better
then nothing but generally is not enough. You need a personal wiki. Frontage can serve as a
surrogate Wiki on windows very well. One of the advantages is that you do not need to learn
special wiki script. You can use HTML. The author uses it in this capacity for 20 year or so.
Bash is so called "glue" language and set of classic Unix utilities represent important
part of bash functionality. So in addition to knowledge of bash you need to know at least several dozens
of Unix utilities (and you better know them well ;-). That requirement alone often lead to 'stack overflow". So mental crutches in the
form of your custom references are needed and creating them is a the necessary step because of
complexity of the environment. Which now is beyond human understanding (the situation called
Blind men and an elephant -
Wikipedia).
At the current level of complexity the fact that bash has source code
available changes absolutely nothing for 99% of Unix/linux sysadmins. It is a classic example
of self-closing open source ;-).
At the beginning a good reference book might suffice too (see
Best Shell Books
for recommendations) but gradually you should create you own website or wiki and work on
enhancing it. Even people with 20 year Unix experience often can't remember of the vital
option of the most common utilities such as ls, grep and find.
Also blunders in bash can be very costly and lead to the lostt of data, crash of the OS or
both. They are often connected iether with misunderstanding of bash behaviour or
misunderstanding of behaviour or one of Unix utilities you use, or Unix itself, or make accidentally
or out of exhaustion or under time pressure some nasty error yourself. See
Sysadmin Horror Stories
The key problem with bash is that it is "rarely used language" for most users. That
means that while users might use bash as a command interpreter daily, they program more
or less complex scripts rarely (say once a quarter on average). And from one time
to another manage to forget most of the important staff related to this language. That makes
programming in bash a real challenge.
While bash has a humble start and much weaker designer then ksh93 and was at
times extremely buggy, the gap narrowed in version 3.2 to the extent that better
command line user friendliness of bash make it more attractive, especially as an
interactive shell then supplied with OS ksh and C-shell. Most ksh93 innovations
are now present in some form in bash 3.2. As bash is standard shell on Linux
and is installed by default in Solaris it status as an enterprise shell is almost
as strong as ksh93 (which is mostly present in old, weaker versions. Current bash
has the best debugger and from this point of view represents the best shell. But
portable script still are probably better to be written for ksh88 or POSIX shell
which is lowest common denominator available on all Unixes. To write scripts in
Borne shell now is extremely stupid and wasteful.
Bash 3.2 and later is the one of the most portable advanced shell around (ksh93
and zsh are still a strong competition; ksh93 is definitely more reliable for scripts
and does not contain such design blunders as the last stage of the pipe belonging
to subshell instead of the invoking shell (actually fixed in lager version of bash).
Bash-related books dominates shell-related publications and as such the level
of known-how for bash is higher then for other shells (see
Best Shell Books).The advantage of bash for large enterprise environment is that it comes
by default with linux, Solaris and AIX (unfortunately in pretty different versions).
Only HP-UX does not have bash installed by default. Also it is the best portable
interactive shell, much closer to tcsh then any competition.
Still you need some efforts to make the default shell. Unfortunately the default
shell for Solaris is the "Bourne shell" or
/usr/bin/sh
Bourne shell is a pretty weak outdated shell and attempt to base shell scripting
portability on this outdated shell is a serious strategic error (bash probably should
be used instead). It is still used as root shell in Solaris ,but that's due to Solaris
legacy not because it gives anything but disadvantages; attempts to claim that this
somehow increases the security of root due to the fact that it is static linked
are weak and the argumentation is open for discussion. All-in-all usage of Bourne
shell as a default root shell in Solaris might be considered to be a blunder: system
administrators definitely need a better shell.
Usage of Borne shall as a default shell might slightly increase the chances of
recovery in case /usr partition
is damaged, but this is a pretty serious case and usually means serious troubles
with other partitions on the disk anyway (unless this is the case when in Solaris
link /bin -> usr/bin is destroyed,
but such cases are simple to fight by refereeing shell as
/usr/bin/ksh in
/etc/passwd). If this is a serious trouble than
booting from a CD a mounting the damaged volume is always a good idea and in this
case it does not matter what shell root is using; you can change it anyway.
Bash 3.2 is reasonably easy to build from source. Get and unpack in your home directory
the archive. This will create a directory called bash-3.2 in your home directory.
If you do not have the gunzip utility, you can obtain it in the same way you obtained
bash or simply use gzip -d instead. The archive contains all of the source code
needed to compile bash and a large amount of documentation and examples. the latter
have their own value.
The bash archive contains a main directory and a set of files and subdirectories.
Among the first files you should examine are:
CHANGES A comprehensive list of bug fixes and new features since the
last version
COPYING The GNU Copyleft for bash
MANIFEST A list of all the files and directories in the archive
NEWS A list of new features since the last version
READMEA short introduction and instructions for compiling
bash
doc Directory with information related to bash in various formats (please note
that many syadmin never read those docs)
examples Directory with examples of startup files, scripts, and functions
The doc directory contains a few articles that are worth reading. You can print
man page for reference with command nroff-man bash.1 | more
It is convenient to have a hardcopy so you can write notes all over it. Also valuable,
FAQ is a Frequently Asked Questions document with answers, readline.3
is the manual entry for the readline facility, and article.ms
is an article about the shell that appeared in Linux Journal, and was written
by the current bash maintainer Chet Ramey.
An examples directory is especially important and is well worth exploring
(after you've finished reading this book, of course). It includes sample code, scripts,
functions, and startup files. See Examples
shipped with bash 3.2 and newer
An examples directory
directory is especially important and is well worth exploring (after you've
finished reading this book, of course). It includes sample code, scripts,
functions, and startup files. See Examples shipped with
bash 3.2 and newer
Some interesting features of bash in version 3.2 and up
Some interesting features of bash include:
Special COMMAND_PROMPT built-in variable
makes it easy to use functions for command prompt generation. For example the
color of the prompt can be changed dynamically depending of the whether you
are root or not and what was the return
code of the last command. See
Bash Prompt
HOWTO
A lot of borrowing from tcsh including:
Built-in pushd/popd/dirs
commands (implementation leaves much to be desired as there is no way to
avoid duplicates like in tcsh, but still it is slightly better then nothing
;-). References to directories based on content of DIRSTACK are also implemented
(cd ~1; cd ~2; etc)
C-shell style history info retrieval:
Arrows based browsing of history works out of the box.
! C-shell style history retrieval. !$ is the last
argument of the previous line
HISTCONTROL may now include the
erasedups option (version
3.0 and higher)
C-shell style arrows keys history browsing
"bang commands" repeatedly, saving you some typing (via the "!<digit>
syntax).
Indexed arrays of unlimited size
Integer arithmetic in any base from two to sixty-four
Availability of =~ operator that permits using more or less normal
regular expressions (it's incredible stupidity to use a dozen of regular expression
definitions in a single OS like is the case with Unix and Linux).
Note:bash before version 3.2.17 has extremely
buggy and unreliable support of this feature. Generally it is better to use 4.x in production
env.
Normal for loop with index. for ((
expr1 ; expr2 ; expr3 )) ; do list ; done
Process substitution (
> (command) and < (command)
). It's not enabled by default. and need
to be enabled by the command set +o posix Two forms of
process substitution are:
<(list) To substitute a command pipeline for
an input file
>(list) To substitute a command pipeline for
an output file
In the case of the < and > forms, the shell will
run process list asynchronously, connected to a named pipe (FIFO).
The name of this pipe will become the argument to the command.
Please visit Heiner
Steven
SHELLdorado
the best shell scripting site on the Internet
If the form with < is selected then result of execution of the
process list will serve as an input file. This, for allows you to use the output
of one or several commands as parameters to the utilities that accepts file.
For instance you can compare the contents of two directories by typing:
diff <( ls dir1 ) <( ls dir2 )
That can be used for concatenating input in pipes:
cat <(echo hello) <(echo world) | echo
If < is used, then the file passed as an argument will be a
named pipe connected to the output of the list process.
cuts fields 1 and 3 from the files file1 and file2 respectively, pastes the
results together, and sends it to the processes process1 and process2. Note
that the file, which is passed as an argument to the command, is a system pipe
so programs that expect to lseek(2) on the file will not work. Also note that
the previous example can be more compactly and efficiently written as:
history and aliases can be used in shell scripts, not only in interective
sessions.
new readline variables: enable-keypad, mark-directories, input-meta,
visible-stats, disable-completion, comment-begin
new readline commands to manipulate the mark and operate on the region
new DEBUG trap
expanded (and now documented) restricted shell mode
Due to this bash shell is gradually gaining grounds as the preferred interactive
shell for Solaris and other enterprise class Unixes.
Older version of bash (2.x series) are obsolete and should not be used. The recommended
version is 3.2 patch level 3 or above. 4.x is recommended on RHEL 6.x and up.
Bash completion is a functionality through which Bash helps users type their commands more
quickly and easily. It does this by presenting possible options when users press the Tab key
while typing a command.
The completion script is code that uses the builtin Bash command complete to
define which completion suggestions can be displayed for a given executable . The nature of the
completion options vary, from simple static to highly sophisticated. Why bother?
This functionality helps users by:
saving them from typing text when it can be auto-completed
helping them know the available continuations to their commands
preventing errors and improving their experience by hiding or showing options based on
what they have already typed
Hands-on
Here's what we will do in this tutorial:
We will first create a dummy executable script called dothis . All it does is
execute the command that resides on the number that was passed as an argument in the user's
history. For example, the following command will simply execute the ls -a command,
given that it exists in history with number 235 :
dothis 235
Then we will create a Bash completion script that will display commands along with their
number from the user's history, and we will "bind" it to the dothis
executable.
$ dothis < tab >< tab >
215 ls
216 ls -la
217 cd ~
218 man history
219 git status
220 history | cut -c 8 - bash_screen.png
Create a file named dothis in your working directory and add the following
code:
if [ -z "$1" ] ; then
echo "No command number passed"
exit 2
fi
exists =$ ( fc -l -1000 | grep ^ $1 -- 2 >/ dev / null )
if [ -n " $exists " ] ; then
fc -s -- "$1"
else
echo "Command with number $1 was not found in recent history"
exit 2
fi
Notes:
We first check if the script was called with an argument
We then check if the specific number is included in the last 1000 commands
if it exists, we execute the command using the fc functionality
otherwise, we display an error message
Make the script executable with:
chmod +x ./dothis
We will execute this script many times in this tutorial, so I suggest you place it in a
folder that is included in your path so that we can access it from anywhere by
typing dothis .
I installed it in my home bin folder using:
install ./dothis ~/bin/dothis
You can do the same given that you have a ~/bin folder and it is included in
your PATH variable.
Check to see if it's working:
dothis
You should see this:
$ dothis
No command number passed
Done.
Creating the completion script
Create a file named dothis-completion.bash . From now on, we will refer to this
file with the term completion script .
Once we add some code to it, we will source it to allow the completion to take
effect. We must source this file every single time we change something in it .
Later in this tutorial, we will discuss our options for registering this script whenever a
Bash shell opens.
Static completion
Suppose that the dothis program supported a list of commands, for example:
now
tomorrow
never
Let's use the complete command to register this list for completion. To use the
proper terminology, we say we use the complete command to define a completion
specification ( compspec ) for our program.
Here's what we specified with the complete command above:
we used the -W ( wordlist ) option to provide a list of words for
completion.
we defined to which "program" these completion words will be used (the
dothis parameter)
Source the file:
source ./dothis-completion.bash
Now try pressing Tab twice in the command line, as shown below:
$ dothis < tab
>< tab >
never now tomorrow
Try again after typing the n :
$ dothis n < tab >< tab >
never now
Magic! The completion options are automatically filtered to match only those starting with
n .
Note: The options are not displayed in the order that we defined them in the word list; they
are automatically sorted.
There are many other options to be used instead of the -W that we used in this
section. Most produce completions in a fixed manner, meaning that we don't intervene
dynamically to filter their output.
For example, if we want to have directory names as completion words for the
dothis program, we would change the complete command to the following:
complete -A directory dothis
Pressing Tab after the dothis program would get us a list of the directories in
the current directory from which we execute the script:
We will be producing the completions of the dothis executable with the
following logic:
If the user presses the Tab key right after the command, we will show the last 50
executed commands along with their numbers in history
If the user presses the Tab key after typing a number that matches more than one command
from history, we will show only those commands along with their numbers in history
If the user presses the Tab key after a number that matches exactly one command in
history, we auto-complete the number without appending the command's literal (if this is
confusing, no worries -- you will understand later)
Let's start by defining a function that will execute each time the user requests completion
on a dothis command. Change the completion script to this:
we used the -F flag in the complete command defining that the
_dothis_completions is the function that will provide the completions of the
dothis executable
COMPREPLY is an array variable used to store the completions -- the
completion mechanism uses this variable to display its contents as completions
Now source the script and go for completion:
$ dothis < tab >< tab >
never now tomorrow
Perfect. We produce the same completions as in the previous section with the word list. Or
not? Try this:
$ dothis nev < tab >< tab >
never now tomorrow
As you can see, even though we type nev and then request for completion, the available
options are always the same and nothing gets completed automatically. Why is this
happening?
The contents of the COMPREPLY variable are always displayed. The function is
now responsible for adding/removing entries from there.
If the COMPREPLY variable had only one element, then that word would be
automatically completed in the command. Since the current implementation always returns the
same three words, this will not happen.
Enter compgen : a builtin command that generates completions supporting most of
the options of the complete command (ex. -W for word list,
-d for directories) and filtering them based on what the user has already
typed.
Don't worry if you feel confused; everything will become clear later.
Type the following in the console to better understand what compgen does:
$
compgen -W "now tomorrow never"
now
tomorrow
never
$ compgen -W "now tomorrow never" n
now
never
$ compgen -W "now tomorrow never" t
tomorrow
So now we can use it, but we need to find a way to know what has been typed after the
dothis command. We already have the way: The Bash completion facilities provide
Bash
variables related to the completion taking place. Here are the more important ones:
COMP_WORDS : an array of all the words typed after the name of the program
the compspec belongs to
COMP_CWORD : an index of the COMP_WORDS array pointing to the
word the current cursor is at -- in other words, the index of the word the cursor was when
the tab key was pressed
COMP_LINE : the current command line
To access the word just after the dothis word, we can use the value of
COMP_WORDS[1]
Now, instead of the words now, tomorrow, never , we would like to see actual
numbers from the command history.
The fc -l command followed by a negative number -n displays the
last n commands. So we will use:
fc -l -50
which lists the last 50 executed commands along with their numbers. The only manipulation we
need to do is replace tabs with spaces to display them properly from the completion mechanism.
sed to the rescue.
We do have a problem, though. Try typing a number as you see it in your completion list and
then press the key again.
$ dothis 623 < tab >
$ dothis 623 ls 623 ls -la
...
$ dothis 623 ls 623 ls 623 ls 623 ls 623 ls -la
This is happening because in our completion script, we used the
${COMP_WORDS[1]} to always check the first typed word after the
dothis command (the number 623 in the above snippet). Hence the
completion continues to suggest the same completion again and again when the Tab key is
pressed.
To fix this, we will not allow any kind of completion to take place if at least one argument
has already been typed. We will add a condition in our function that checks the size of the
aforementioned COMP_WORDS array.
#/usr/bin/env bash
_dothis_completions ()
{
if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
return
fi
$ dothis 623 < tab >
$ dothis 623 ls -la < tab > # SUCCESS: nothing happens here
There is another thing we don't like, though. We do want to display the numbers along with
the corresponding commands to help users decide which one is desired, but when there is only
one completion suggestion and it gets automatically picked by the completion mechanism, we
shouldn't append the command literal too .
In other words, our dothis executable accepts only a number, and we haven't
added any functionality to check or expect other arguments. When our completion function gives
only one result, we should trim the command literal and respond only with the command
number.
To accomplish this, we will keep the response of the compgen command in an
array variable, and if its size is 1 , we will trim the one and only element to keep just the
number. Otherwise, we'll let the array as is.
Change the completion script to this:
#/usr/bin/env bash
_dothis_completions ()
{
if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
return
fi
# keep the suggestions in a local variable
local suggestions = ( $ ( compgen -W " $(fc -l -50 | sed 's/\t/ /') " -- " ${COMP_WORDS[1]} "
))
if [ " ${#suggestions[@]} " == "1" ] ; then
# if there's only one match, we remove the command literal
# to proceed with the automatic completion of the number
local number =$ ( echo ${suggestions[0]/%\ */} )
COMPREPLY = ( " $number " )
else
# more than one suggestions resolved,
# respond with the suggestions intact
COMPREPLY = ( " ${suggestions[@]} " )
fi
}
complete -F _dothis_completions dothis
Registering the completion script
If you want to enable the completion just for you on your machine, all you have to do is add
a line in your .bashrc file sourcing the script:
If you want to enable the completion for all users, you can just copy the script under
/etc/bash_completion.d/ and it will automatically be loaded by
Bash.
Fine-tuning the completion script
Here are some extra steps for better results:
Displaying each entry in a new line
In the Bash completion script I was working on, I too had to present suggestions consisting
of two parts. I wanted to display the first part in the default color and the second part in
gray to distinguish it as help text. In this tutorial's example, it would be nice to present
the numbers in the default color and the command literal in a less fancy one.
Unfortunately, this is not possible, at least for now, because the completions are displayed
as plain text and color directives are not processed (for example: \e[34mBlue
).
What we can do to improve the user experience (or not) is to display each entry in a new
line. This solution is not that obvious since we can't just append a new line character in each
COMPREPLY entry. We will follow a rather
hackish method and pad suggestion literals to a width that fills the terminal.
Enter printf . If you want to display each suggestion on each own line, change
the completion script to the following:
#/usr/bin/env bash
_dothis_completions ()
{
if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
return
fi
local IFS =$ '\n'
local suggestions = ( $ ( compgen -W " $(fc -l -50 | sed 's/\t//') " -- " ${COMP_WORDS[1]} "
))
if [ " ${#suggestions[@]} " == "1" ] ; then
local number = " ${suggestions[0]/%\ */} "
COMPREPLY = ( " $number " )
else
for i in " ${!suggestions[@]} " ; do
suggestions [ $i ] = " $(printf '%*s' "-$COLUMNS" "${suggestions[$i]}") "
done
In our case, we hard-coded to display the last 50 commands for completion. This is not a
good practice. We should first respect what each user might prefer. If he/she hasn't made any
preference, we should default to 50.
To accomplish that, we will check if an environment variable
DOTHIS_COMPLETION_COMMANDS_NUMBER has been set.
Change the completion script one last time:
#/usr/bin/env bash
_dothis_completions ()
{
if [ " ${#COMP_WORDS[@]} " ! = "2" ] ; then
return
fi
local commands_number = ${DOTHIS_COMPLETION_COMMANDS_NUMBER:-50}
local IFS =$ '\n'
local suggestions = ( $ ( compgen -W " $(fc -l -$commands_number | sed 's/\t//') " -- "
${COMP_WORDS[1]} " ))
if [ " ${#suggestions[@]} " == "1" ] ; then
local number = " ${suggestions[0]/%\ */} "
COMPREPLY = ( " $number " )
else
for i in " ${!suggestions[@]} " ; do
suggestions [ $i ] = " $(printf '%*s' "-$COLUMNS" "${suggestions[$i]}") "
done
You can find the code of this tutorial on GitHub .
For feedback, comments, typos, etc., please open an issue in the
repository.
Lazarus Lazaridis - I am an open source enthusiast and I like helping developers with
tutorials and tools . I usually code
in Ruby especially when it's on Rails but I also speak Java, Go, bash & C#. I have studied
CS at Athens University of Economics and Business and I live in Athens, Greece. My nickname is
iridakos and I publish tech related posts on my personal blog iridakos.com .
Bash Range: How to iterate over sequences generated on the shell 2 days ago You can iterate the sequence of numbers in
bash by two ways. One is by using seq command and another is by specifying range in for loop. In seq command, the sequence starts
from one, the number increments by one in each step and print each number in each line up to the upper limit by default. If the number
starts from upper limit then it decrements by one in each step. Normally, all numbers are interpreted as floating point but if the
sequence starts from integer then the list of decimal integers will print. If seq command can execute successfully then it returns
0, otherwise it returns any non-zero number. You can also iterate the sequence of numbers using for loop with range. Both seq command
and for loop with range are shown in this tutorial by using examples.
The options of seq command:
You can use seq command by using the following options.
-w This option is used to pad the numbers with leading zeros to print all numbers with equal width.
-f format This option is used to print number with particular format. Floating number can be formatted by using %f,
%g and %e as conversion characters. %g is used as default.
-s string This option is used to separate the numbers with string. The default value is newline ('\n').
Examples of seq command:
You can apply seq command by three ways. You can use only upper limit or upper and lower limit or upper and lower limit with increment
or decrement value of each step . Different uses of the seq command with options are shown in the following examples.
Example-1: seq command without option
When only upper limit is used then the number will start from 1 and increment by one in each step. The following command
will print the number from 1 to 4.
$ seq 4
When the two values are used with seq command then first value will be used as starting number and second value will be used as
ending number. The following command will print the number from 7 to 15.
$ seq 7 15
When you will use three values with seq command then the second value will be used as increment or decrement value for each step.
For the following command, the starting number is 10, ending number is 1 and each step will be counted by decrementing 2.
$ seq 10 -2 1
Example-2: seq with –w option
The following command will print the output by adding leading zero for the number from 1 to 9.
$ seq -w 0110
Example-3: seq with –s option
The following command uses "-" as separator for each sequence number. The sequence of numbers will print by adding "-" as separator.
$ seq -s - 8
Example-4: seq with -f option
The following command will print 10 date values starting from 1. Here, "%g" option is used to add sequence number with other string
value.
$ seq -f "%g/04/2018" 10
The following command is used to generate the sequence of floating point number using "%f" . Here, the number will start from
3 and increment by 0.8 in each step and the last number will be less than or equal to 6.
$ seq -f "%f" 3 0.8 6
Example-5: Write the sequence in a file
If you want to save the sequence of number into a file without printing in the console then you can use the following commands.
The first command will print the numbers to a file named " seq.txt ". The number will generate from 5 to 20 and increment by 10 in
each step. The second command is used to view the content of " seq.txt" file.
seq 5 10 20 | cat > seq.txt
cat seq.txt
Example-6: Using seq in for loop
Suppose, you want to create files named fn1 to fn10 using for loop with seq. Create a file named "sq1.bash" and add the following
code. For loop will iterate for 10 times using seq command and create 10 files in the sequence fn1, fn2,fn3 ..fn10.
#!/bin/bash
for i in ` seq 10 ` ; do
touch fn. $i
done
Run the following commands to execute the code of the bash file and check the files are created or not.
bash sq1.bash
ls
Examples of for loop with range:Example-7: For loop with range
The alternative of seq command is range. You can use range in for loop to generate sequence of numbers like seq. Write the following
code in a bash file named " sq2.bash ". The loop will iterate for 5 times and print the square root of each number in each step.
#!/bin/bash
for n in { 1 .. 5 } ; do
(( result =n * n ))
echo $n square = $result
done
Run the command to execute the script of the file.
bash sq2.bash
Example-8: For loop with range and increment value
By default, the number is increment by one in each step in range like seq. You can also change the increment value in range. Write
the following code in a bash file named " sq3.bash ". The for loop in the script will iterate for 5 times, each step is incremented
by 2 and print all odd numbers between 1 to 10.
#!/bin/bash
echo "all odd numbers from 1 to 10 are"
for i in { 1 .. 10 .. 2 }; do
echo $i ;
done
Run the command to execute the script of the file.
bash sq3.bash
If you want to work with the sequence of numbers then you can use any of the options that are shown in this tutorial. After completing
this tutorial, you will be able to use seq command and for loop with range more efficiently in your bash script.
For example, if you have a directory ~/Documents/Phone-Backup/Linux-Docs/Ubuntu/ , using
gogo , you can create an alias (a shortcut name), for instance Ubuntu to access it
without typing the whole path anymore. No matter your current working directory, you can move
into ~/cd Documents/Phone-Backup/Linux-Docs/Ubuntu/ by simply using the alias
Ubuntu .
In addition, it also allows you to create aliases for connecting directly into directories
on remote Linux servers.
How to Install Gogo in Linux Systems
To install Gogo , first clone the gogo repository from Github and then copy the
gogo.py to any directory in your PATH environmental variable (if you already have
the ~/bin/ directory, you can place it here, otherwise create it).
$ git clone https://github.com/mgoral/gogo.git
$ cd gogo/
$ mkdir -p ~/bin #run this if you do not have ~/bin directory
$ cp gogo.py ~/bin/
... ... ...
To start using gogo , you need to logout and login back to use it. Gogo
stores its configuration in ~/.config/gogo/gogo.conf file (which should be auto
created if it doesn't exist) and has the following syntax.
# Comments are lines that start from '#' character.
default = ~/something
alias = /desired/path
alias2 = /desired/path with space
alias3 = "/this/also/works"
zażółć = "unicode/is/also/supported/zażółć gęślą jaźń"
If you run gogo run without any arguments, it will go to the directory specified in default;
this alias is always available, even if it's not in the configuration file, and points to $HOME
directory.
"... Lukas Jelinek is the author of the incron package that allows users to specify tables of inotify events that are executed by the master incrond process. Despite the reference to "cron", the package does not schedule events at regular intervals -- it is a tool for filesystem events, and the cron reference is slightly misleading. ..."
"... The incron package is available from EPEL ..."
It is, at times, important to know when things change in the Linux OS. The uses to which
systems are placed often include high-priority data that must be processed as soon as it is
seen. The conventional method of finding and processing new file data is to poll for it,
usually with cron. This is inefficient, and it can tax performance unreasonably if too many
polling events are forked too often.
Linux has an efficient method for alerting user-space processes to changes impacting files
of interest. The inotify Linux system calls were first discussed here in Linux Journal
in a 2005 article by Robert
Love who primarily addressed the behavior of the new features from the perspective of
C.
However, there also are stable shell-level utilities and new classes of monitoring
dæmons for registering filesystem watches and reporting events. Linux installations using
systemd also can access basic inotify functionality with path units. The inotify interface does
have limitations -- it can't monitor remote, network-mounted filesystems (that is, NFS); it
does not report the userid involved in the event; it does not work with /proc or other
pseudo-filesystems; and mmap() operations do not trigger it, among other concerns. Even with
these limitations, it is a tremendously useful feature.
This article completes the work begun by Love and gives everyone who can write a Bourne
shell script or set a crontab the ability to react to filesystem changes.
The inotifywait
Utility
Working under Oracle Linux 7 (or similar versions of Red Hat/CentOS/Scientific Linux), the
inotify shell tools are not installed by default, but you can load them with yum:
# yum install inotify-tools
Loaded plugins: langpacks, ulninfo
ol7_UEKR4 | 1.2 kB 00:00
ol7_latest | 1.4 kB 00:00
Resolving Dependencies
--> Running transaction check
---> Package inotify-tools.x86_64 0:3.14-8.el7 will be installed
--> Finished Dependency Resolution
Dependencies Resolved
==============================================================
Package Arch Version Repository Size
==============================================================
Installing:
inotify-tools x86_64 3.14-8.el7 ol7_latest 50 k
Transaction Summary
==============================================================
Install 1 Package
Total download size: 50 k
Installed size: 111 k
Is this ok [y/d/N]: y
Downloading packages:
inotify-tools-3.14-8.el7.x86_64.rpm | 50 kB 00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Warning: RPMDB altered outside of yum.
Installing : inotify-tools-3.14-8.el7.x86_64 1/1
Verifying : inotify-tools-3.14-8.el7.x86_64 1/1
Installed:
inotify-tools.x86_64 0:3.14-8.el7
Complete!
The package will include two utilities (inotifywait and inotifywatch), documentation and a
number of libraries. The inotifywait program is of primary interest.
Some derivatives of Red Hat 7 may not include inotify in their base repositories. If you
find it missing, you can obtain it from Fedora's EPEL repository , either by downloading the
inotify RPM for manual installation or adding the EPEL repository to yum.
Any user on the system who can launch a shell may register watches -- no special privileges
are required to use the interface. This example watches the /tmp directory:
$ inotifywait -m /tmp
Setting up watches.
Watches established.
If another session on the system performs a few operations on the files in /tmp:
A few relevant sections of the manual page explain what is happening:
$ man inotifywait | col -b | sed -n '/diagnostic/,/helpful/p'
inotifywait will output diagnostic information on standard error and
event information on standard output. The event output can be config-
ured, but by default it consists of lines of the following form:
watched_filename EVENT_NAMES event_filename
watched_filename
is the name of the file on which the event occurred. If the
file is a directory, a trailing slash is output.
EVENT_NAMES
are the names of the inotify events which occurred, separated by
commas.
event_filename
is output only when the event occurred on a directory, and in
this case the name of the file within the directory which caused
this event is output.
By default, any special characters in filenames are not escaped
in any way. This can make the output of inotifywait difficult
to parse in awk scripts or similar. The --csv and --format
options will be helpful in this case.
It also is possible to filter the output by registering particular events of interest with
the -e option, the list of which is shown here:
access
create
move_self
attrib
delete
moved_to
close_write
delete_self
moved_from
close_nowrite
modify
open
close
move
unmount
A common application is testing for the arrival of new files. Since inotify must be given
the name of an existing filesystem object to watch, the directory containing the new files is
provided. A trigger of interest is also easy to provide -- new files should be complete and
ready for processing when the close_write trigger fires. Below is an example
script to watch for these events:
#!/bin/sh
unset IFS # default of space, tab and nl
# Wait for filesystem events
inotifywait -m -e close_write \
/tmp /var/tmp /home/oracle/arch-orcl/ |
while read dir op file
do [[ "${dir}" == '/tmp/' && "${file}" == *.txt ]] &&
echo "Import job should start on $file ($dir $op)."
[[ "${dir}" == '/var/tmp/' && "${file}" == CLOSE_WEEK*.txt ]] &&
echo Weekly backup is ready.
[[ "${dir}" == '/home/oracle/arch-orcl/' && "${file}" == *.ARC ]]
&&
su - oracle -c 'ORACLE_SID=orcl ~oracle/bin/log_shipper' &
[[ "${dir}" == '/tmp/' && "${file}" == SHUT ]] && break
((step+=1))
done
echo We processed $step events.
There are a few problems with the script as presented -- of all the available shells on
Linux, only ksh93 (that is, the AT&T Korn shell) will report the "step" variable correctly
at the end of the script. All the other shells will report this variable as null.
The reason for this behavior can be found in a brief explanation on the manual page for
Bash: "Each command in a pipeline is executed as a separate process (i.e., in a subshell)." The
MirBSD clone of the Korn shell has a slightly longer explanation:
# man mksh | col -b | sed -n '/The parts/,/do so/p'
The parts of a pipeline, like below, are executed in subshells. Thus,
variable assignments inside them fail. Use co-processes instead.
foo | bar | read baz # will not change $baz
foo | bar |& read -p baz # will, however, do so
And, the pdksh documentation in Oracle Linux 5 (from which MirBSD mksh emerged) has several
more mentions of the subject:
General features of at&t ksh88 that are not (yet) in pdksh:
- the last command of a pipeline is not run in the parent shell
- `echo foo | read bar; echo $bar' prints foo in at&t ksh, nothing
in pdksh (ie, the read is done in a separate process in pdksh).
- in pdksh, if the last command of a pipeline is a shell builtin, it
is not executed in the parent shell, so "echo a b | read foo bar"
does not set foo and bar in the parent shell (at&t ksh will).
This may get fixed in the future, but it may take a while.
$ man pdksh | col -b | sed -n '/BTW, the/,/aware/p'
BTW, the most frequently reported bug is
echo hi | read a; echo $a # Does not print hi
I'm aware of this and there is no need to report it.
This behavior is easy enough to demonstrate -- running the script above with the default
bash shell and providing a sequence of example events:
# ./inotify.sh
Setting up watches.
Watches established.
Import job should start on newdata.txt (/tmp/ CLOSE_WRITE,CLOSE).
Weekly backup is ready.
We processed events.
Examining the process list while the script is running, you'll also see two shells, one
forked for the control structure:
$ function pps { typeset a IFS=\| ; ps ax | while read a
do case $a in *$1*|+([!0-9])) echo $a;; esac; done }
$ pps inot
PID TTY STAT TIME COMMAND
3394 pts/1 S+ 0:00 /bin/sh ./inotify.sh
3395 pts/1 S+ 0:00 inotifywait -m -e close_write /tmp /var/tmp
3396 pts/1 S+ 0:00 /bin/sh ./inotify.sh
As it was manipulated in a subshell, the "step" variable above was null when control flow
reached the echo. Switching this from #/bin/sh to #/bin/ksh93 will correct the problem, and
only one shell process will be seen:
# ./inotify.ksh93
Setting up watches.
Watches established.
Import job should start on newdata.txt (/tmp/ CLOSE_WRITE,CLOSE).
Weekly backup is ready.
We processed 2 events.
$ pps inot
PID TTY STAT TIME COMMAND
3583 pts/1 S+ 0:00 /bin/ksh93 ./inotify.sh
3584 pts/1 S+ 0:00 inotifywait -m -e close_write /tmp /var/tmp
Although ksh93 behaves properly and in general handles scripts far more gracefully than all
of the other Linux shells, it is rather large:
The mksh binary is the smallest of the Bourne implementations above (some of these shells
may be missing on your system, but you can install them with yum). For a long-term monitoring
process, mksh is likely the best choice for reducing both processing and memory footprint, and
it does not launch multiple copies of itself when idle assuming that a coprocess is used.
Converting the script to use a Korn coprocess that is friendly to mksh is not difficult:
#!/bin/mksh
unset IFS # default of space, tab and nl
# Wait for filesystem events
inotifywait -m -e close_write \
/tmp/ /var/tmp/ /home/oracle/arch-orcl/ \
2</dev/null |& # Launch as Korn coprocess
while read -p dir op file # Read from Korn coprocess
do [[ "${dir}" == '/tmp/' && "${file}" == *.txt ]] &&
print "Import job should start on $file ($dir $op)."
[[ "${dir}" == '/var/tmp/' && "${file}" == CLOSE_WEEK*.txt ]] &&
print Weekly backup is ready.
[[ "${dir}" == '/home/oracle/arch-orcl/' && "${file}" == *.ARC ]]
&&
su - oracle -c 'ORACLE_SID=orcl ~oracle/bin/log_shipper' &
[[ "${dir}" == '/tmp/' && "${file}" == SHUT ]] && break
((step+=1))
done
echo We processed $step events.
Flush its standard output whenever it writes a message.
An fflush(NULL) is found in the main processing loop of the inotifywait source,
and these requirements appear to be met.
The mksh version of the script is the most reasonable compromise for efficient use and
correct behavior, and I have explained it at some length here to save readers trouble and
frustration -- it is important to avoid control structures executing in subshells in most of
the Borne family. However, hopefully all of these ersatz shells someday fix this basic flaw and
implement the Korn behavior correctly.
A Practical Application -- Oracle Log Shipping
Oracle databases that are configured for hot backups produce a stream of "archived redo log
files" that are used for database recovery. These are the most critical backup files that are
produced in an Oracle database.
These files are numbered sequentially and are written to a log directory configured by the
DBA. An inotifywatch can trigger activities to compress, encrypt and/or distribute the archived
logs to backup and disaster recovery servers for safekeeping. You can configure Oracle RMAN to
do most of these functions, but the OS tools are more capable, flexible and simpler to use.
There are a number of important design parameters for a script handling archived logs:
A "critical section" must be established that allows only a single process to manipulate
the archived log files at a time. Oracle will sometimes write bursts of log files, and
inotify might cause the handler script to be spawned repeatedly in a short amount of time.
Only one instance of the handler script can be allowed to run -- any others spawned during
the handler's lifetime must immediately exit. This will be achieved with a textbook
application of the flock program from the util-linux package.
The optimum compression available for production applications appears to be lzip . The author claims that the integrity of
his archive format is superior to many more well known
utilities , both in compression ability and also structural integrity. The lzip binary is
not in the standard repository for Oracle Linux -- it is available in EPEL and is easily
compiled from source.
Note that 7-Zip uses the same LZMA
algorithm as lzip, and it also will perform AES encryption on the data after compression.
Encryption is a desirable feature, as it will exempt a business from
breach disclosure laws in most US states if the backups are lost or stolen and they
contain "Protected Personal Information" (PPI), such as birthdays or Social Security Numbers.
The author of lzip does have harsh things to say regarding the quality of 7-Zip archives
using LZMA2, and the openssl enc program can be used to apply AES encryption
after compression to lzip archives or any other type of file, as I discussed in a previous
article . I'm foregoing file encryption in the script below and using lzip for
clarity.
The current log number will be recorded in a dot file in the Oracle user's home
directory. If a log is skipped for some reason (a rare occurrence for an Oracle database),
log shipping will stop. A missing log requires an immediate and full database backup (either
cold or hot) -- successful recoveries of Oracle databases cannot skip logs.
The scp program will be used to copy the log to a remote server, and it
should be called repeatedly until it returns successfully.
I'm calling the genuine '93 Korn shell for this activity, as it is the most capable
scripting shell and I don't want any surprises.
Given these design parameters, this is an implementation:
# cat ~oracle/archutils/process_logs
#!/bin/ksh93
set -euo pipefail
IFS=$'\n\t' # http://redsymbol.net/articles/unofficial-bash-strict-mode/
(
flock -n 9 || exit 1 # Critical section-allow only one process.
ARCHDIR=~oracle/arch-${ORACLE_SID}
APREFIX=${ORACLE_SID}_1_
ASUFFIX=.ARC
CURLOG=$(<~oracle/.curlog-$ORACLE_SID)
File="${ARCHDIR}/${APREFIX}${CURLOG}${ASUFFIX}"
[[ ! -f "$File" ]] && exit
while [[ -f "$File" ]]
do ((NEXTCURLOG=CURLOG+1))
NextFile="${ARCHDIR}/${APREFIX}${NEXTCURLOG}${ASUFFIX}"
[[ ! -f "$NextFile" ]] && sleep 60 # Ensure ARCH has finished
nice /usr/local/bin/lzip -9q "$File"
until scp "${File}.lz" "yourcompany.com:~oracle/arch-$ORACLE_SID"
do sleep 5
done
CURLOG=$NEXTCURLOG
File="$NextFile"
done
echo $CURLOG > ~oracle/.curlog-$ORACLE_SID
) 9>~oracle/.processing_logs-$ORACLE_SID
The above script can be executed manually for testing even while the inotify handler is
running, as the flock protects it.
A standby server, or a DataGuard server in primitive standby mode, can apply the archived
logs at regular intervals. The script below forces a 12-hour delay in log application for the
recovery of dropped or damaged objects, so inotify cannot be easily used in this case -- cron
is a more reasonable approach for delayed file processing, and a run every 20 minutes will keep
the standby at the desired recovery point:
# cat ~oracle/archutils/delay-lock.sh
#!/bin/ksh93
(
flock -n 9 || exit 1 # Critical section-only one process.
WINDOW=43200 # 12 hours
LOG_DEST=~oracle/arch-$ORACLE_SID
OLDLOG_DEST=$LOG_DEST-applied
function fage { print $(( $(date +%s) - $(stat -c %Y "$1") ))
} # File age in seconds - Requires GNU extended date & stat
cd $LOG_DEST
of=$(ls -t | tail -1) # Oldest file in directory
[[ -z "$of" || $(fage "$of") -lt $WINDOW ]] && exit
for x in $(ls -rt) # Order by ascending file mtime
do if [[ $(fage "$x") -ge $WINDOW ]]
then y=$(basename $x .lz) # lzip compression is optional
[[ "$y" != "$x" ]] && /usr/local/bin/lzip -dkq "$x"
$ORACLE_HOME/bin/sqlplus '/ as sysdba' > /dev/null 2>&1 <<-EOF
recover standby database;
$LOG_DEST/$y
cancel
quit
EOF
[[ "$y" != "$x" ]] && rm "$y"
mv "$x" $OLDLOG_DEST
fi
done
) 9> ~oracle/.recovering-$ORACLE_SID
I've covered these specific examples here because they introduce tools to control
concurrency, which is a common issue when using inotify, and they advance a few features that
increase reliability and minimize storage requirements. Hopefully enthusiastic readers will
introduce many improvements to these approaches.
The incron System
Lukas Jelinek is the author of the incron package that allows users to specify tables of
inotify events that are executed by the master incrond process. Despite the reference to
"cron", the package does not schedule events at regular intervals -- it is a tool for
filesystem events, and the cron reference is slightly misleading.
The incron package is available from EPEL . If you have installed the repository,
you can load it with yum:
# yum install incron
Loaded plugins: langpacks, ulninfo
Resolving Dependencies
--> Running transaction check
---> Package incron.x86_64 0:0.5.10-8.el7 will be installed
--> Finished Dependency Resolution
Dependencies Resolved
=================================================================
Package Arch Version Repository Size
=================================================================
Installing:
incron x86_64 0.5.10-8.el7 epel 92 k
Transaction Summary
==================================================================
Install 1 Package
Total download size: 92 k
Installed size: 249 k
Is this ok [y/d/N]: y
Downloading packages:
incron-0.5.10-8.el7.x86_64.rpm | 92 kB 00:01
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Installing : incron-0.5.10-8.el7.x86_64 1/1
Verifying : incron-0.5.10-8.el7.x86_64 1/1
Installed:
incron.x86_64 0:0.5.10-8.el7
Complete!
On a systemd distribution with the appropriate service units, you can start and enable
incron at boot with the following commands:
# systemctl start incrond
# systemctl enable incrond
Created symlink from
/etc/systemd/system/multi-user.target.wants/incrond.service
to /usr/lib/systemd/system/incrond.service.
In the default configuration, any user can establish incron schedules. The incrontab format
uses three fields:
<path> <mask> <command>
Below is an example entry that was set with the -e option:
While the IN_CLOSE_WRITE event on a directory object is usually of greatest
interest, most of the standard inotify events are available within incron, which also offers
several unique amalgams:
$ man 5 incrontab | col -b | sed -n '/EVENT SYMBOLS/,/child process/p'
EVENT SYMBOLS
These basic event mask symbols are defined:
IN_ACCESS File was accessed (read) (*)
IN_ATTRIB Metadata changed (permissions, timestamps, extended
attributes, etc.) (*)
IN_CLOSE_WRITE File opened for writing was closed (*)
IN_CLOSE_NOWRITE File not opened for writing was closed (*)
IN_CREATE File/directory created in watched directory (*)
IN_DELETE File/directory deleted from watched directory (*)
IN_DELETE_SELF Watched file/directory was itself deleted
IN_MODIFY File was modified (*)
IN_MOVE_SELF Watched file/directory was itself moved
IN_MOVED_FROM File moved out of watched directory (*)
IN_MOVED_TO File moved into watched directory (*)
IN_OPEN File was opened (*)
When monitoring a directory, the events marked with an asterisk (*)
above can occur for files in the directory, in which case the name
field in the returned event data identifies the name of the file within
the directory.
The IN_ALL_EVENTS symbol is defined as a bit mask of all of the above
events. Two additional convenience symbols are IN_MOVE, which is a com-
bination of IN_MOVED_FROM and IN_MOVED_TO, and IN_CLOSE, which combines
IN_CLOSE_WRITE and IN_CLOSE_NOWRITE.
The following further symbols can be specified in the mask:
IN_DONT_FOLLOW Don't dereference pathname if it is a symbolic link
IN_ONESHOT Monitor pathname for only one event
IN_ONLYDIR Only watch pathname if it is a directory
Additionally, there is a symbol which doesn't appear in the inotify sym-
bol set. It is IN_NO_LOOP. This symbol disables monitoring events until
the current one is completely handled (until its child process exits).
The incron system likely presents the most comprehensive interface to inotify of all the
tools researched and listed here. Additional configuration options can be set in
/etc/incron.conf to tweak incron's behavior for those that require a non-standard
configuration.
Path Units under systemd
When your Linux installation is running systemd as PID 1, limited inotify functionality is
available through "path units" as is discussed in a lighthearted article by Paul Brown
at OCS-Mag .
The relevant manual page has useful information on the subject:
$ man systemd.path | col -b | sed -n '/Internally,/,/systems./p'
Internally, path units use the inotify(7) API to monitor file systems.
Due to that, it suffers by the same limitations as inotify, and for
example cannot be used to monitor files or directories changed by other
machines on remote NFS file systems.
Note that when a systemd path unit spawns a shell script, the $HOME and tilde (
~ ) operator for the owner's home directory may not be defined. Using the tilde
operator to reference another user's home directory (for example, ~nobody/) does work, even
when applied to the self-same user running the script. The Oracle script above was explicit and
did not reference ~ without specifying the target user, so I'm using it as an example here.
Using inotify triggers with systemd path units requires two files. The first file specifies
the filesystem location of interest:
The PathChanged parameter above roughly corresponds to the
close-write event used in my previous direct inotify calls. The full collection of
inotify events is not (currently) supported by systemd -- it is limited to
PathExists , PathChanged and PathModified , which are
described in man systemd.path .
The second file is a service unit describing a program to be executed. It must have the same
name, but a different extension, as the path unit:
The oneshot parameter above alerts systemd that the program that it forks is
expected to exit and should not be respawned automatically -- the restarts are limited to
triggers from the path unit. The above service configuration will provide the best options for
logging -- divert them to /dev/null if they are not needed.
Use systemctl start on the path unit to begin monitoring -- a common error is
using it on the service unit, which will directly run the handler only once. Enable the path
unit if the monitoring should survive a reboot.
Although this limited functionality may be enough for some casual uses of inotify, it is a
shame that the full functionality of inotifywait and incron are not represented here. Perhaps
it will come in time.
Conclusion
Although the inotify tools are powerful, they do have limitations. To repeat them, inotify
cannot monitor remote (NFS) filesystems; it cannot report the userid involved in a triggering
event; it does not work with /proc or other pseudo-filesystems; mmap() operations do not
trigger it; and the inotify queue can overflow resulting in lost events, among other
concerns.
Even with these weaknesses, the efficiency of inotify is superior to most other approaches
for immediate notifications of filesystem activity. It also is quite flexible, and although the
close-write directory trigger should suffice for most usage, it has ample tools for covering
special use cases.
In any event, it is productive to replace polling activity with inotify watches, and system
administrators should be liberal in educating the user community that the classic crontab is
not an appropriate place to check for new files. Recalcitrant users should be confined to
Ultrix on a VAX until they develop sufficient appreciation for modern tools and approaches,
which should result in more efficient Linux systems and happier administrators.
Sidenote:
Archiving /etc/passwd
Tracking changes to the password file involves many different types of inotify triggering
events. The vipw utility commonly will make changes to a temporary file, then
clobber the original with it. This can be seen when the inode number changes:
# ll -i /etc/passwd
199720973 -rw-r--r-- 1 root root 3928 Jul 7 12:24 /etc/passwd
# vipw
[ make changes ]
You are using shadow passwords on this system.
Would you like to edit /etc/shadow now [y/n]? n
# ll -i /etc/passwd
203784208 -rw-r--r-- 1 root root 3956 Jul 7 12:24 /etc/passwd
The destruction and replacement of /etc/passwd even occurs with setuid binaries called by
unprivileged users:
For this reason, all inotify triggering events should be considered when tracking this file.
If there is concern with an inotify queue overflow (in which events are lost), then the
OPEN , ACCESS and CLOSE_NOWRITE,CLOSE triggers likely
can be immediately ignored.
All other inotify events on /etc/passwd might run the following script to version the
changes into an RCS archive and mail them to an administrator:
#!/bin/sh
# This script tracks changes to the /etc/passwd file from inotify.
# Uses RCS for archiving. Watch for UID zero.
PWMAILS=Charlie.Root@openbsd.org
TPDIR=~/track_passwd
cd $TPDIR
if diff -q /etc/passwd $TPDIR/passwd
then exit # they are the same
else sleep 5 # let passwd settle
diff /etc/passwd $TPDIR/passwd 2>&1 | # they are DIFFERENT
mail -s "/etc/passwd changes $(hostname -s)" "$PWMAILS"
cp -f /etc/passwd $TPDIR # copy for checkin
# "SCCS, the source motel! Programs check in and never check out!"
# -- Ken Thompson
rcs -q -l passwd # lock the archive
ci -q -m_ passwd # check in new ver
co -q passwd # drop the new copy
fi > /dev/null 2>&1
Here is an example email from the script for the above chfn operation:
-----Original Message-----
From: root [mailto:root@myhost.com]
Sent: Thursday, July 06, 2017 2:35 PM
To: Fisher, Charles J. <Charles.Fisher@myhost.com>;
Subject: /etc/passwd changes myhost
57c57
< fishecj:x:123:456:Fisher, Charles J.:/home/fishecj:/bin/bash
---
> fishecj:x:123:456:Fisher, Charles J.:/home/fishecj:/bin/csh
Further processing on the third column of /etc/passwd might detect UID zero (a root user) or
other important user classes for emergency action. This might include a rollback of the file
from RCS to /etc and/or SMS messages to security contacts. ______________________
Charles Fisher has an electrical engineering degree from the University of Iowa and works as
a systems and database administrator for a Fortune 500 mining and manufacturing
corporation.
BASH Shell: How To Redirect stderr To stdout ( redirect stderr to a File ) Posted on
March 12,
2008 March 12, 2008 in Categories BASH Shell , Linux , UNIX last updated March 12, 2008 Q. How do I
redirect stderr to stdout? How do I redirect stderr to a file?
A. Bash and other modern shell provides I/O redirection facility. There are 3 default
standard files (standard streams) open:
[a] stdin – Use to get input (keyboard) i.e. data going into a program.
[b] stdout – Use to write information (screen)
[c] stderr – Use to write error message (screen)
Understanding I/O streams
numbers
The Unix / Linux standard I/O streams with numbers:
Handle
Name
Description
0
stdin
Standard input
1
stdout
Standard output
2
stderr
Standard error
Redirecting the standard error stream to a file
The following will redirect program error message to a file called error.log: $ program-name 2> error.log
$ command1 2> error.log
Redirecting the standard error (stderr) and stdout to
file
Use the following syntax: $ command-name &>file
OR $ command > file-name 2>&1
Another useful example: # find /usr/home -name .profile 2>&1 | more
A more flexible method for defining custom commands for an interactive shell (or within a script)
is to use a shell function. We could declare our ll function in a Bash startup file
as a function instead of an alias like so:
# Shortcut to call ls(1) with the -l flag
ll() {
command ls -l "$@"
}
Note the use of the command builtin here to specify that the ll function
should invoke the program named ls , and not any function named
ls . This is particularly important when writing a function wrapper around a command,
to stop an infinite loop where the function calls itself indefinitely:
# Always add -q to invocations of gdb(1)
gdb() {
command gdb -q "$@"
}
In both examples, note also the use of the "$@" expansion, to add to the final command
line any arguments given to the function. We wrap it in double quotes to stop spaces and other shell
metacharacters in the arguments causing problems. This means that the ll command will
work correctly if you were to pass it further options and/or one or more directories as arguments:
$ ll -a
$ ll ~/.config
Shell functions declared in this way are specified by POSIX for Bourne-style shells, so they should
work in your shell of choice, including Bash, dash , Korn shell, and Zsh. They can also
be used within scripts, allowing you to abstract away multiple instances of similar commands to improve
the clarity of your script, in much the same way the basics of functions work in general-purpose
programming languages.
Functions are a good and portable way to approach adding features to your interactive shell; written
carefully, they even allow you to port features you might like from other shells into your shell
of choice. I'm fond of taking commands I like from Korn shell or Zsh and implementing them in Bash
or POSIX shell functions, such as Zsh's
vared or its
two-argument
cd features.
If you end up writing a lot of shell functions, you should consider putting them into
separate configuration
subfiles to keep your shell's primary startup file from becoming unmanageably large.
Examples from the author
You can take a look at some of the shell functions I have defined here that are useful to me in
general shell usage; a lot of these amount to implementing convenience features that I wish my shell
had, especially for quick directory navigation, or adding options to commands:
You can manipulate variables within shell functions, too:
# Print the filename of a path, stripping off its leading path and
# extension
fn() {
name=$1
name=${name##*/}
name=${name%.*}
printf '%s\n' "$name"
}
This works fine, but the catch is that after the function is done, the value for name
will still be defined in the shell, and will overwrite whatever was in there previously:
This may be desirable if you actually want the function to change some aspect of your current
shell session, such as managing variables or changing the working directory. If you don't
want that, you will probably want to find some means of avoiding name collisions in your variables.
If your function is only for use with a shell that provides the local (Bash) or
typeset (Ksh) features, you can declare the variable as local to the function to remove
its global scope, to prevent this happening:
# Bash-like
fn() {
local name
name=$1
name=${name##*/}
name=${name%.*}
printf '%s\n' "$name"
}
# Ksh-like
# Note different syntax for first line
function fn {
typeset name
name=$1
name=${name##*/}
name=${name%.*}
printf '%s\n' "$name"
}
If you're using a shell that lacks these features, or you want to aim for POSIX compatibility,
things are a little trickier, since local function variables aren't specified by the standard. One
option is to use a subshell , so
that the variables are only defined for the duration of the function:
# POSIX; note we're using plain parentheses rather than curly brackets, for
# a subshell
fn() (
name=$1
name=${name##*/}
name=${name%.*}
printf '%s\n' "$name"
)
# POSIX; alternative approach using command substitution:
fn() {
printf '%s\n' "$(
name=$1
name=${name##*/}
name=${name%.*}
printf %s "$name"
)"
}
This subshell method also allows you to change directory with cd within a function
without changing the working directory of the user's interactive shell, or to change shell options
with set or Bash options with shopt only temporarily for the purposes of
the function.
Another method to deal with variables is to manipulate the
positional parameters directly ( $1 , $2 ) with set ,
since they are local to the function call too:
# POSIX; using positional parameters
fn() {
set -- "${1##*/}"
set -- "${1%.*}"
printf '%s\n' "$1"
}
These methods work well, and can sometimes even be combined, but they're awkward to write, and
harder to read than the modern shell versions. If you only need your functions to work with your
modern shell, I recommend just using local or typeset . The Bash Guide
on Greg's Wiki has a
very thorough
breakdown of functions in Bash, if you want to read about this and other aspects of functions
in more detail.
Keeping functions for later
As you get comfortable with defining and using functions during an interactive session, you might
define them in ad-hoc ways on the command line for calling in a loop or some other similar circumstance,
just to solve a task in that moment.
As an example, I recently made an ad-hoc function called monit to run a set of commands
for its hostname argument that together established different types of monitoring system checks,
using an existing script called nmfs :
$ monit() { nmfs "$1" Ping Y ; nmfs "$1" HTTP Y ; nmfs "$1" SNMP Y ; }
$ for host in webhost{1..10} ; do
> monit "$host"
> done
After that task was done, I realized I was likely to use the monit command interactively
again, so I decided to keep it. Shell functions only last as long as the current shell, so if you
want to make them permanent, you need to store their definitions somewhere in your startup files.
If you're using Bash, and you're content to just add things to the end of your ~/.bashrc
file, you could just do something like this:
$ declare -f monit >> ~/.bashrc
That would append the existing definition of monit in parseable form to your
~/.bashrc file, and the monit function would then be loaded and available
to you for future interactive sessions. Later on, I ended up converting monit into a
shell script, as its use wasn't limited to just an interactive shell.
If you want a more robust approach to keeping functions like this for Bash permanently, I wrote
a tool called Bashkeep , which allows you to quickly store functions and variables defined in
your current shell into separate and appropriately-named files, including viewing and managing the
list of names conveniently:
For tools
like diff that work with multiple files as parameters, it can be useful to work
with not just files on the filesystem, but also potentially with the output of arbitrary
commands. Say, for example, you wanted to compare the output of ps and ps
-e with diff -u . An obvious way to do this is to write files to compare
the output:
This works just fine, but Bash provides a shortcut in the form of process
substitution , allowing you to treat the standard output of commands as files. This is
done with the <() and >() operators. In our case, we want to
direct the standard output of two commands into place as files:
$ diff -u <(ps) <(ps -e)
This is functionally equivalent, except it's a little tidier because it doesn't leave files
lying around. This is also very handy for elegantly comparing files across servers, using
ssh :
$ diff -u .bashrc <(ssh remote cat .bashrc)
Conversely, you can also use the >() operator to direct from a filename
context to the standard input of a command. This is handy for setting up in-place
filters for things like logs. In the following example, I'm making a call to rsync
, specifying that it should make a log of its actions in log.txt , but filter it
through grep -vF .tmp first to remove anything matching the fixed string
.tmp :
Combined with tee this syntax is a way of simulating multiple filters for a
stdout stream, transforming output from a command in as many ways as you see
fit:
In general, the idea is that wherever on the command line you could specify a file to be
read from or written to, you can instead use this syntax to make an implicit named pipe for the
text stream.
Thanks to Reddit user Rhomboid for pointing out an incorrect assertion about this syntax
necessarily abstractingmkfifocalls, which I've since removed.
With judicious use of tricks like pipes, redirects, and process substitution in modern shells, it's
very often possible to avoid using temporary files, doing everything inline and keeping them quite
neat. However when manipulating a lot of data into various formats you do find yourself occasionally
needing a temporary file, just to hold data temporarily.
A common way to deal with this is to create a temporary file in your home directory, with some
arbitrary name, something like test or working :
$ ps -ef >~/test
If you want to save the information indefinitely for later use, this makes sense, although it
would be better to give it a slightly more instructive name than just test .
If you really only needed the data temporarily, however, you're much better to use the temporary
files directory. This is usually /tmp , but for good practice's sake it's better to
check the value of TMPDIR first, and only use /tmp as a default:
$ ps -ef >"${TMPDIR:-/tmp}"/test
This is getting better, but there is still a significant problem: there's no built-in check that
the test file doesn't already exist, perhaps being used by some other user or program,
particularly another running instance of the same script.
To that end, we have the mktemp
program, which creates an empty temporary file in the appropriate directory for you without overwriting
anything, and prints the filename it created. This allows you to use the file inline in both shell
scripts and one-liners, and is much safer than specifying hardcoded paths:
On GNU/Linux systems, files of a sufficient age in TMPDIR are cleared on boot (controlled
in /etc/default/rcS on Debian-derived systems, /etc/cron.daily/tmpwatch
on Red Hat ones), making /tmp useful as a general scratchpad as well as for a kind of
relatively reliable inter-process communication without cluttering up users' home directories.
In some cases, there may be additional advantages in using /tmp for its designed
purpose as some administrators choose to mount it as a tmpfs filesystem, so it operates
in RAM and works very quickly. It's also common practice to set the noexec flag on the
mount to prevent malicious users from executing any code they manage to find or save in the directory.
"... One of my favourite technical presentations I've read online has been Hal Pomeranz's Unix Command-Line Kung Fu , a catalogue of shortcuts and efficient methods of doing very clever things with the Bash shell. None of these are grand arcane secrets, but they're things that are often forgotten in the course of daily admin work, when you find yourself typing something you needn't, or pressing up repeatedly to find something you wrote for which you could simply search your command history. ..."
One of my favourite
technical presentations I've read online has been Hal Pomeranz's Unix Command-Line Kung
Fu , a catalogue of shortcuts and efficient methods of doing very clever things with the
Bash shell. None of these are grand arcane secrets, but they're things that are often forgotten
in the course of daily admin work, when you find yourself typing something you needn't, or
pressing up repeatedly to find something you wrote for which you could simply search your
command history.
I highly recommend reading the whole thing, as I think even the most experienced shell users
will find there are useful tidbits in there that would make their lives easier and their time
with the shell more productive, beyond simpler things like tab completion.
Here, I'll recap two
of the things I thought were the most simple and useful items in the presentation for general
shell usage, and see if I can add a little value to them with reference to the Bash
manual.
History with Ctrl+R
For many shell users, finding a command in history means either pressing the up arrow key
repeatedly, or perhaps piping a history call through grep . It turns
out there's a much nicer way to do this, using Bash's built-in history searching functionality;
if you press Ctrl+R and start typing a search pattern, the most recent command matching that
pattern will automatically be inserted on your current line, at which point you can adapt it as
you need, or simply press Enter to run it again. You can keep pressing Ctrl+R to move further
back in your history to the next-most recent match. On my shell, if I search through my history
for git , I can pull up what I typed for a previous commit:
This functionality isn't actually exclusive to Bash; you can establish a history search
function in quite a few tools that use GNU Readline, including the MySQL client command
line.
You can search forward through history in the same way with Ctrl+S, but it's likely you'll
have to fix up a couple of terminal annoyances first.
Additionally, if like me you're a Vim user and you don't really like having to reach for the
arrow keys, or if you're on a terminal where those keys are broken for whatever reason, you can
browse back and forth within your command history with Ctrl+P (previous) and Ctrl+N (next).
These are just a few of the Emacs-style shortcuts that GNU Readline provides; check here for a more complete
list .
Repeating commands with !!
The last command you ran in Bash can be abbreviated on the next line with two exclamation
marks:
$ echo "Testing."
Testing.
$ !!
Testing.
You can use this to simply repeat a command over and over again, although for that you
really should be using watch , but more interestingly it turns out
this is very handy for building complex pipes in stages. Suppose you were building a pipeline
to digest some data generated from a program like netstat , perhaps to determine
the top 10 IP addresses that are holding open the most connections to a server. You might be
able to build a pipeline like this:
Similarly, you can repeat the last argument from the previous command line using
!$ , which is useful if you're doing a set of operations on one file, such as
checking it out via RCS, editing it, and checking it back in:
$ co -l file.txt
$ vim !$
$ ci -u !$
Or if you happen to want to work on a set of arguments, you can repeat all of the
arguments from the previous command using !* :
$ touch a.txt b.txt c.txt
$ rm !*
When you remember to user these three together, they can save you a lot of typing, and will
really increase your accuracy because you won't be at risk of mistyping any of the commands or
arguments. Naturally, however, it pays to be careful what you're running through
rm !
When you have some
spare time, something instructive to do that can help fill gaps in your Unix knowledge and to
get a better idea of the programs installed on your system and what they can do is a simple
whatis call, run
over all the executable files in your /bin and /usr/bin directories.
This will give you a one-line summary of the file's function if available from man pages.
tom@conan:/bin$ whatis *
bash (1) - GNU Bourne-Again SHell
bunzip2 (1) - a block-sorting file compressor, v1.0.4
busybox (1) - The Swiss Army Knife of Embedded Linux
bzcat (1) - decompresses files to stdout
...
tom@conan:/usr/bin$ whatis *
[ (1) - check file types and compare values
2to3 (1) - Python2 to Python3 converter
2to3-2.7 (1) - Python2 to Python3 converter
411toppm (1) - convert Sony Mavica .411 image to ppm
...
It also works on many of the files in other directories, such as /etc :
tom@conan:/etc$ whatis *
acpi (1) - Shows battery status and other ACPI information
adduser.conf (5) - configuration file for adduser(8) and addgroup(8)
adjtime (3) - correct the time to synchronize the system clock
aliases (5) - Postfix local alias database format
...
Because packages often install more than one binary and you're only in the habit of using
one or two of them, this process can tell you about programs on your system that you may have
missed, particularly standard tools that solve common problems. As an example, I first learned
about watch this
way, having used a clunky solution with for loops with sleep calls to
do the same thing many times before.
In Bash
scripting (and shell scripting in general), we often want to check the exit value of a command
to decide an action to take after it completes, likely for the purpose of error handling. For
example, to determine whether a particular regular expression regex was present
somewhere in a file options , we might apply grep(1) with its POSIX
-q option to suppress output and just use the exit value:
grep -q regex options
An approach sometimes taken is then to test the exit value with the $?
parameter, using if to check if it's non-zero, which is not very elegant and a bit
hard to read:
# Bad practice
grep -q regex options
if (($? > 0)); then
printf '%s\n' 'myscript: Pattern not found!' >&2
exit 1
fi
Because the if construct by design
tests the exit value of commands , it's better to test the command directly ,
making the expansion of $? unnecessary:
# Better
if grep -q regex options; then
# Do nothing
:
else
printf '%s\n' 'myscript: Pattern not found!\n' >&2
exit 1
fi
We can precede the command to be tested with ! to negate the test as
well, to prevent us having to use else as well:
# Best
if ! grep -q regex options; then
printf '%s\n' 'myscript: Pattern not found!' >&2
exit 1
fi
An alternative syntax is to use && and || to perform
if and else tests with grouped commands between braces, but these
tend to be harder to read:
# Alternative
grep -q regex options || {
printf '%s\n' 'myscript: Pattern not found!' >&2
exit 1
}
With this syntax, the two commands in the block are only executed if the
grep(1) call exits with a non-zero status. We can apply &&
instead to execute commands if it does exit with zero.
That syntax can be convenient for quickly short-circuiting failures in scripts, for example
due to nonexistent commands, particularly if the command being tested already outputs its own
error message. This therefore cuts the script off if the given command fails, likely due to
ffmpeg(1) being unavailable on the system:
hash ffmpeg || exit 1
Note that the braces for a grouped command are not needed here, as there's only one command
to be run in case of failure, the exit call.
Calls to cd are another good use case here, as running a script in the wrong
directory if a call to cd fails could have really nasty effects:
cd wherever || exit 1
In general, you'll probably only want to test $? when you have
specific non-zero error conditions to catch. For example, if we were using the
--max-delete option for rsync(1) , we could check a call's return
value to see whether rsync(1) hit the threshold for deleted file count and write a
message to a logfile appropriately:
rsync --archive --delete --max-delete=5 source destination
if (($? == 25)); then
printf '%s\n' 'Deletion limit was reached' >"$logfile"
fi
It may be tempting to use the errexit feature in the hopes of stopping a script
as soon as it encounters any error, but there are some problems with its usage that make it a bit
error-prone. It's generally more straightforward to simply write your own error handling using
the methods above.
For a really thorough breakdown of dealing with conditionals in Bash, take a look at the
relevant chapter of the Bash Guide .
"... Note that we unset the config variable after we're done, otherwise it'll be in the namespace of our shell where we don't need it. You may also wish to check for the existence of the ~/.bashrc.d directory, check there's at least one matching file inside it, or check that the file is readable before attempting to source it, depending on your preference. ..."
"... Thanks to commenter oylenshpeegul for correcting the syntax of the loops. ..."
Large shell startup scripts ( .bashrc , .profile ) over about fifty
lines or so with a lot of options, aliases, custom functions, and similar tweaks can get cumbersome
to manage over time, and if you keep your dotfiles under version control it's not terribly helpful
to see large sets of commits just editing the one file when it could be more instructive if broken
up into files by section.
Given that shell configuration is just shell code, we can apply the source builtin
(or the . builtin for POSIX sh ) to load several files at the end of a
.bashrc , for example:
This is a better approach, but it still binds us into using those filenames; we still have to
edit the ~/.bashrc file if we want to rename them, or remove them, or add new ones.
Fortunately, UNIX-like systems have a common convention for this, the .d directory
suffix, in which sections of configuration can be stored to be read by a main configuration file
dynamically. In our case, we can create a new directory ~/.bashrc.d :
$ ls ~/.bashrc.d
options.bash
aliases.bash
functions.bash
With a slightly more advanced snippet at the end of ~/.bashrc , we can then load
every file with the suffix .bash in this directory:
# Load any supplementary scripts
for config in "$HOME"/.bashrc.d/*.bash ; do
source "$config"
done
unset -v config
Note that we unset the config variable after we're done, otherwise it'll be in the
namespace of our shell where we don't need it. You may also wish to check for the existence of the
~/.bashrc.d directory, check there's at least one matching file inside it, or check
that the file is readable before attempting to source it, depending on your preference.
The same method can be applied with .profile to load all scripts with the suffix
.sh in ~/.profile.d , if we want to write in POSIX sh , with
some slightly different syntax:
# Load any supplementary scripts
for config in "$HOME"/.profile.d/*.sh ; do
. "$config"
done
unset -v config
Another advantage of this method is that if you have your dotfiles under version control, you
can arrange to add extra snippets on a per-machine basis unversioned, without having to update your
.bashrc file.
Here's my implementation of the above method, for both .bashrc and .profile
:
By default, the
Bash shell keeps the history of your most recent session in the .bash_history
file, and the commands you've issued in your current session are also available with a
history call. These defaults are useful for keeping track of what you've been up
to in the shell on any given machine, but with disks much larger and faster than they were when
Bash was designed, a little tweaking in your .bashrc file can record history more
permanently, consistently, and usefully. Append history instead of rewriting it
You should start by setting the histappend option, which will mean that when
you close a session, your history will be appended to the .bash_history
file rather than overwriting what's in there.
shopt -s histappend
Allow a larger history file
The default maximum number of commands saved into the .bash_history file is a
rather meager 500. If you want to keep history further back than a few weeks or so, you may as
well bump this up by explicitly setting $HISTSIZE to a much larger number in your
.bashrc . We can do the same thing with the $HISTFILESIZE
variable.
HISTFILESIZE=1000000
HISTSIZE=1000000
The man page for Bash says that HISTFILESIZE can be
unset to stop truncation entirely, but unfortunately this doesn't work in
.bashrc files due to the order in which variables are set; it's therefore more
straightforward to simply set it to a very large number.
If you're on a machine with resource constraints, it might be a good idea to occasionally
archive old .bash_history files to speed up login and reduce memory
footprint.
Don't store specific lines
You can prevent commands that start with a space from going into history by setting
$HISTCONTROL to ignorespace . You can also ignore duplicate commands,
for example repeated du calls to watch a file grow, by adding
ignoredups . There's a shorthand to set both in ignoreboth .
HISTCONTROL=ignoreboth
You might also want to remove the use of certain commands from your history, whether for
privacy or readability reasons. This can be done with the $HISTIGNORE variable.
It's common to use this to exclude ls calls, job control builtins like
bg and fg , and calls to history itself:
HISTIGNORE='ls:bg:fg:history'
Record timestamps
If you set $HISTTIMEFORMAT to something useful, Bash will record the timestamp
of each command in its history. In this variable you can specify the format in which you want
this timestamp displayed when viewed with history . I find the full date and time
to be useful, because it can be sorted easily and works well with tools like cut
and awk .
HISTTIMEFORMAT='%F %T '
Use one command per line
To make your .bash_history file a little easier to parse, you can force
commands that you entered on more than one line to be adjusted to fit on only one with the
cmdhist option:
shopt -s cmdhist
Store history immediately
By default, Bash only records a session to the .bash_history file on disk when
the session terminates. This means that if you crash or your session terminates improperly, you
lose the history up to that point. You can fix this by recording each line of history as you
issue it, through the $PROMPT_COMMAND variable:
Setting the Bash
option histexpand allows some convenient typing shortcuts using Bash history
expansion . The option can be set with either of these:
$ set -H
$ set -o histexpand
It's likely that this option is already set for all interactive shells, as it's on by
default. The manual, man bash , describes these features as follows:
-H Enable ! style history substitution. This option is on
by default when the shell is interactive.
You may have come across this before, perhaps to your annoyance, in the following error
message that comes up whenever ! is used in a double-quoted string, or without
being escaped with a backslash:
$ echo "Hi, this is Tom!"
bash: !": event not found
If you don't want the feature and thereby make ! into a normal character, it
can be disabled with either of these:
$ set +H
$ set +o histexpand
History expansion is actually a very old feature of shells, having been available in
csh before Bash usage became common.
This article is a good followup to Better Bash history , which among
other things explains how to include dates and times in history output, as these
examples do.
Basic history expansion
Perhaps the best known and most useful of these expansions is using !! to refer
to the previous command. This allows repeating commands quickly, perhaps to monitor the
progress of a long process, such as disk space being freed while deleting a large file:
$ rm big_file &
[1] 23608
$ du -sh .
3.9G .
$ !!
du -sh .
3.3G .
It can also be useful to specify the full filesystem path to programs that aren't in your
$PATH :
$ hdparm
-bash: hdparm: command not found
$ /sbin/!!
/sbin/hdparm
In each case, note that the command itself is printed as expanded, and then run to print the
output on the following line.
History by absolute index
However, !! is actually a specific example of a more general form of history
expansion. For example, you can supply the history item number of a specific command to repeat
it, after looking it up with history :
$ history | grep expand
3951 2012-08-16 15:58:53 set -o histexpand
$ !3951
set -o histexpand
You needn't enter the !3951 on a line by itself; it can be included as any part
of the command, for example to add a prefix like sudo :
$ sudo !3850
If you include the escape string \! as part of your Bash prompt , you can include the current
command number in the prompt before the command, making repeating commands by index a lot
easier as long as they're still visible on the screen.
History by relative index
It's also possible to refer to commands relative to the current command. To
subtitute the second-to-last command, we can type !-2 . For example, to check
whether truncating a file with sed worked correctly:
This works further back into history, with !-3 , !-4 , and so
on.
Expanding for historical arguments
In each of the above cases, we're substituting for the whole command line. There are also
ways to get specific tokens, or words , from the command if we want that. To get the
first argument of a particular command in the history, use the !^
token:
$ touch a.txt b.txt c.txt
$ ls !^
ls a.txt
a.txt
To get the last argument, add !$ :
$ touch a.txt b.txt c.txt
$ ls !$
ls c.txt
c.txt
To get all arguments (but not the command itself), use !* :
$ touch a.txt b.txt c.txt
$ ls !*
ls a.txt b.txt c.txt
a.txt b.txt c.txt
This last one is particularly handy when performing several operations on a group of files;
we could run du and wc over them to get their size and character
count, and then perhaps decide to delete them based on the output:
More generally, you can use the syntax !n:w to refer to any specific argument
in a history item by number. In this case, the first word, usually a command or builtin, is
word 0 :
$ history | grep bash
4073 2012-08-16 20:24:53 man bash
$ !4073:0
man
What manual page do you want?
$ !4073:1
bash
You can even select ranges of words by separating their indices with a hyphen:
If you want to match any part of the command line, not just the start, you can use
!?string? :
$ !?bash?
man bash
Be careful when using these, if you use them at all. By default it will run the most recent
command matching the string immediately , with no prompting, so it might be a problem
if it doesn't match the command you expect.
Checking history expansions before
running
If you're paranoid about this, Bash allows you to audit the command as expanded before you
enter it, with the histverify option:
This option works for any history expansion, and may be a good choice for more cautious
administrators. It's a good thing to add to one's .bashrc if so.
If you don't need this set all the time, but you do have reservations at some point about
running a history command, you can arrange to print the command without running it by adding a
:p suffix:
$ !rm:p
rm important-file
In this instance, the command was expanded, but thankfully not actually
run.
Substituting strings in history expansions
To get really in-depth, you can also perform substitutions on arbitrary commands from the
history with !!:gs/pattern/replacement/ . This is getting pretty baroque even for
Bash, but it's possible you may find it useful at some point:
$ !!:gs/txt/mp3/
rm a.mp3 b.mp3 c.mp3
If you only want to replace the first occurrence, you can omit the g :
$ !!:s/txt/mp3/
rm a.mp3 b.txt c.txt
Stripping leading directories or trailing files
If you want to chop a filename off a long argument to work with the directory, you can do
this by adding an :h suffix, kind of like a dirname call in Perl:
$ du -sh /home/tom/work/doc.txt
$ cd !$:h
cd /home/tom/work
To do the opposite, like a basename call in Perl, use :t :
$ ls /home/tom/work/doc.txt
$ document=!$:t
document=doc.txt
Stripping extensions or base names
A bit more esoteric, but still possibly useful; to strip a file's extension, use
:r :
$ vi /home/tom/work/doc.txt
$ stripext=!$:r
stripext=/home/tom/work/doc
To do the opposite, to get only the extension, use :e :
$ vi /home/tom/work/doc.txt
$ extonly=!$:e
extonly=.txt
Quoting history
If you're performing substitution not to execute a command or fragment but to use it as a
string, it's likely you'll want to quote it. For example, if you've just found through
experiment and trial and error an ideal ffmpeg command line to accomplish some
task, you might want to save it for later use by writing it to a script:
In this case, this will prevent Bash from executing the command expansion "$(date ...
)" , instead writing it literally to the file as desired. If you build a lot of complex
commands interactively that you later write to scripts once completed, this feature is really
helpful and saves a lot of cutting and pasting.
Thanks to commenter Mihai Maruseac for pointing out a bug in the examples.
"... If you're using Bash version 4.0 or above ( bash --version ), you can save a bit of terminal
space by setting the PROMPT_DIRTRIM variable for the shell. This limits the length of the tail end of
the \w and \W expansions to that number of path elements: ..."
The common default of some variant of \h:\w\$ for a
Bash promptPS1
string includes the \w escape character, so that the user's current working directory
appears in the prompt, but with $HOME shortened to a tilde:
This is normally very helpful, particularly if you leave your shell for a time and forget where
you are, though of course you can always call the pwd shell builtin. However it can
get annoying for very deep directory hierarchies, particularly if you're using a smaller terminal
window:
If you're using Bash version 4.0 or above ( bash --version ), you can save a
bit of terminal space by setting the PROMPT_DIRTRIM variable for the shell. This limits
the length of the tail end of the \w and \W expansions to that number of
path elements:
This is a good thing to include in your ~/.bashrc file if you often find yourself
deep in directory trees where the upper end of the hierarchy isn't of immediate interest to you.
You can remove the effect again by unsetting the variable:
Trap syntax is very simple and easy to understand: first we must call the trap builtin, followed
by the action(s) to be executed, then we must specify the signal(s) we want to react to:
trap [-lp] [[arg] sigspec]
Let's see what the possible trap options are for.
When used with the -l flag, the trap command will just display a list of signals
associated with their numbers. It's the same output you can obtain running the kill -l
command:
It's really important to specify that it's possible to react only to signals which allows the script
to respond: the SIGKILL and SIGSTOP signals cannot be caught, blocked or
ignored.
Apart from signals, traps can also react to some pseudo-signal such as EXIT, ERR
or DEBUG, but we will see them in detail later. For now just remember that a signal can be specified
either by its number or by its name, even without the SIG prefix.
About the -p option now. This option has sense only when a command is not provided
(otherwise it will produce an error). When trap is used with it, a list of the previously set traps
will be displayed. If the signal name or number is specified, only the trap set for that specific
signal will be displayed, otherwise no distinctions will be made, and all the traps will be displayed:
$ trap 'echo "SIGINT caught!"' SIGINT
We set a trap to catch the SIGINT signal: it will just display the "SIGINT caught" message onscreen
when given signal will be received by the shell. If we now use trap with the -p option, it will display
the trap we just defined:
$ trap -p
trap -- 'echo "SIGINT caught!"' SIGINT
By the way, the trap is now "active", so if we send a SIGINT signal, either using the kill command,
or with the CTRL-c shortcut, the associated command in the trap will be executed (^C is just printed
because of the key combination):
^CSIGINT caught!
Trap in action We now will write a simple script to show trap in action, here it is:
#!/usr/bin/env bash
#
# A simple script to demonstrate how trap works
#
set -e
set -u
set -o pipefail
trap 'echo "signal caught, cleaning..."; rm -i linux_tarball.tar.xz' SIGINT SIGTERM
echo "Downloading tarball..."
wget -O linux_tarball.tar.xz https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.13.5.tar.xz &> /dev/null
The above script just tries to download the latest linux kernel tarball into the directory from what
it is launched using wget . During the task, if the SIGINT or SIGTERM signals are received
(notice how you can specify more than one signal on the same line), the partially downloaded file
will be deleted.
In this case the command are actually two: the first is the echo which prints the
message onscreen, and the second is the actual rm command (we provided the -i option
to it, so it will ask user confirmation before removing), and they are separated by a semicolon.
Instead of specifying commands this way, you can also call functions: this would give you more re-usability.
Notice that if you don't provide any command the signal(s) will just be ignored!
This is the output of the script above when it receives a SIGINT signal:
A very important thing to remember is that when a script is terminated by a signal, like above, its
exist status will be the result of 128 + the signal number . As you can see, the script
above, being terminated by a SIGINT, has an exit status of 130 :
$ echo $?
130
Lastly, you can disable a trap just by calling trap followed by the - sign,
followed by the signal(s) name or number:
trap - SIGINT SIGTERM
The signals will take back the value they had upon the entrance to shell. Pseudo-signals As
already mentioned above, trap can be set not only for signals which allows the script to respond
but also to what we can call "pseudo-signals". They are not technically signals, but correspond to
certain situations that can be specified: EXIT When EXIT is specified in a trap,
the command of the trap will be execute on exit from the shell. ERR This will cause the argument
of the trap to be executed when a command returns a non-zero exit status, with some exceptions (the
same of the shell errexit option): the command must not be part of a while or
until loop; it must not be part of an if construct, nor part of a &&
or || list, and its value must not be inverted by using the ! operator.
DEBUG This will cause the argument of the trap to be executed before every simple command,
for , case or select commands, and before the first command
in shell functions RETURN The argument of the trap is executed after a function or a script
sourced by using source or the . command.
"... Backquotes ( ` ` ) are old-style form of command substitution, with some differences: in this form, backslash retains its literal meaning except when followed by $ , ` , or \ , and the first backquote not preceded by a backslash terminates the command substitution; whereas in the $( ) form, all characters between the parentheses make up the command, none are treated specially. ..."
"... Double square brackets delimit a Conditional Expression. And, I find the following to be a good reading on the subject: "(IBM) Demystify test, [, [[, ((, and if-then-else" ..."
What you've written actually almost works (it would work if all the variables were numbers), but
it's not an idiomatic way at all.
( ) parentheses indicate a
subshell . What's inside them isn't an expression like in many other languages. It's a
list of commands (just like outside parentheses). These commands are executed in a separate
subprocess, so any redirection, assignment, etc. performed inside the parentheses has no effect
outside the parentheses.
With a leading dollar sign, $( ) is a
command substitution : there is a command inside the parentheses, and the output from
the command is used as part of the command line (after extra expansions unless the substitution
is between double quotes, but that's
another story ).
{ } braces are like parentheses in that they group commands, but they only
influence parsing, not grouping. The program x=2; { x=4; }; echo $x prints 4,
whereas x=2; (x=4); echo $x prints 2. (Also braces require spaces around them
and a semicolon before closing, whereas parentheses don't. That's just a syntax quirk.)
With a leading dollar sign, ${VAR} is a
parameter expansion , expanding to the value of a variable, with possible extra transformations.
(( )) double parentheses surround an
arithmetic instruction , that is, a computation on integers, with a syntax resembling other
programming languages. This syntax is mostly used for assignments and in conditionals.
The same syntax is used in arithmetic expressions $(( )) , which expand
to the integer value of the expression.
[[ ]] double brackets surround
conditional expressions . Conditional expressions are mostly built on
operators such as -n $variable to test if a variable is empty and -e
$file to test if a file exists. There are also string equality operators: "$string1"
= "$string2" (beware that the right-hand side is a pattern, e.g. [[ $foo = a*
]] tests if $foo starts with a while [[ $foo = "a*"
]] tests if $foo is exactly a* ), and the familiar !
, && and || operators for negation, conjunction and disjunction as
well as parentheses for grouping.
Note that you need a space around each operator (e.g. [[ "$x" = "$y" ]]
, not [[ "$x"="$y" ]] ), and a space or a character like ;
both inside and outside the brackets (e.g. [[ -n $foo ]] , not [[-n
$foo]] ).
[ ] single brackets are an alternate form of conditional expressions with
more quirks (but older and more portable). Don't write any for now; start worrying about them
when you find scripts that contain them.
This is the idiomatic way to write your test in bash:
if [[ $varA = 1 && ($varB = "t1" || $varC = "t2") ]]; then
If you need portability to other shells, this would be the way (note the additional quoting
and the separate sets of brackets around each individual test):
+1 @WillSheppard for yr reminder of proper style. Gilles, don't you need a semicolon after yr
closing curly bracket and before "then" ? I always thought if , then
, else and fi could not be on the same line... As in:
Backquotes ( ` ` ) are old-style form of command substitution, with some differences:
in this form, backslash retains its literal meaning except when followed by $ ,
` , or \ , and the first backquote not preceded by a backslash terminates
the command substitution; whereas in the $( ) form, all characters between the parentheses
make up the command, none are treated specially.
You could emphasize that single brackets have completely different semantics inside and outside
of double brackets. (Because you start with explicitly pointing out the subshell semantics but
then only as an aside mention the grouping semantics as part of conditional expressions. Was confusing
to me for a second when I looked at your idiomatic example.) –
Peter A. Schneider
Aug 28 at 13:16
Just to be sure: The quoting in 't1' is unnecessary, right? Because as opposed to arithmetic instructions
in double parentheses, where t1 would be a variable, t1 in a conditional expression in double
brackets is just a literal string.
"... ...and if you weren't targeting a known/fixed operating system, using case rather than a regex match is very much the better practice, since the accepted answer depends on behavior POSIX doesn't define. ..."
"... Regular expression syntax, including the use of backquoting, is different for different tools. Always look it up. ..."
As an aside, if you were using bash for this, the preferred alternative would be the
=~ operator in [[ ]] , ie. [[ Unauthenticated123 =~
^(Unauthenticated|Authenticated) ]] – Charles DuffyDec
14 '15 at 18:22
...and if you weren't targeting a known/fixed operating system, using case
rather than a regex match is very much the better practice, since the accepted answer depends
on behavior POSIX doesn't define. – Charles DuffyDec
14 '15 at 18:25
expr match Unauthenticated123 'Unauthenticated\|Authenticated'
If you want the number of characters matched.
To have the part of the string (Unauthenticated) returned use:
expr match Unauthenticated123 '\(Unauthenticated\|Authenticated\)'
From info coreutils 'expr invocation' :
STRING : REGEX' Perform pattern matching. The arguments are converted to strings
and the second is considered to be a (basic, a la GNU grep') regular expression,
with a `^' implicitly prepended. The first argument is then matched against this regular
expression.
If the match succeeds and REGEX uses `\(' and `\)', the `:'
expression returns the part of STRING that matched the
subexpression; otherwise, it returns the number of characters
matched.
If the match fails, the `:' operator returns the null string if
`\(' and `\)' are used in REGEX, otherwise 0.
Only the first `\( ... \)' pair is relevant to the return value;
additional pairs are meaningful only for grouping the regular
expression operators.
In the regular expression, `\+', `\?', and `\|' are operators
which respectively match one or more, zero or one, or separate
alternatives. SunOS and other `expr''s treat these as regular
characters. (POSIX allows either behavior.) *Note Regular
Expression Library: (regex)Top, for details of regular expression
syntax. Some examples are in *note Examples of expr::.
Note that both match and \| are GNU extensions (and the behaviour
for : (the match standard equivalent) when the pattern starts with
^ varies with implementations). Standardly, you'd do:
The leading space is to avoid problems with values of $string that start with
- or are expr operators, but that means it adds one to the number
of characters being matched.
The + forces $string to be taken as a string even if it happens
to be a expr operator. expr regular expressions are basic regular
expressions which don't have an alternation operator (and where | is not
special). The GNU implementation has it as \| though as an extension.
If all you want is to check whether $string starts with
Authenticated or Unauthenticated , you'd better use:
case $string in
(Authenticated* | Unauthenticated*) do-something
esac
@mikeserv, match and \| are GNU extensions anyway. This Q&A
seems to be about GNU expr anyway (where ^ is guaranteed to mean
match at the beginning of the string ). – Stéphane
ChazelasDec
14 '15 at 14:34
@StéphaneChazelas - i didn't know they were strictly GNU. i think i remember them
being explicitly officially unspecified - but i don't use expr too often
anyway and didn't know that. thank you. – mikeservDec
14 '15 at 14:49
It's not "strictly GNU" - it's present in a number of historical implementations (even System
V had it, undocumented, though it didn't have the others like substr/length/index), which is
why it's explicitly unspecified. I can't find anything about \| being an
extension. – Random832Dec
14 '15 at 16:13
Opens another terminal window at the current location.
Use Case
I often cd into a directory and decide it would be useful to open another terminal in
the same folder, maybe for an editor or something. Previously, I would open the terminal
and repeat the CD command.
I have aliased this command to open so I just type open and I get a new
terminal already in my desired folder.
The & disown part of the command stops the new terminal from being
dependant on the first meaning that you can still use the first and if you close the
first, the second will remain open. Limitations
It relied on you having the $TERMINAL global variable set. If you don't have this set
you could easily change it to something like the following:
While the original one-liner is indeed IMHO the canonical way to loop over numbers,
the brace expansion syntax of Bash 4.x has some kick-ass features such as correct padding
of the number with leading zeros. Limitations
This is similar to seq , but portable. seq does not
exist in all systems and is not recommended today anymore. Other variations to
emulate various uses with seq :
# seq 1 2 10
for ((i=1; i<=10; i+=2)); do echo $i; done
# seq -w 5 10
for ((i=5; i<=10; ++i)); do printf '%02d\n' $i; done
The -i parameter is to edit the file in-place. Limitations
This works as posted in GNU sed . In BSD sed , the
-i flag requires a parameter to use as the suffix of a backup file. You can
set it to empty to not use a backup file:
Am I missing something, or does your last example (in Bash) actually do something completely different?
It works for "ABX", but if you instead make word="Hi All" like the other examples,
it returns ha , not hi all . It only works for the capitalized letters
and skips the already-lowercased letters. –
jangosteve
Jan 14 '12 at 21:58
tr '[:upper:]' '[:lower:]' will use the current locale to determine uppercase/lowercase
equivalents, so it'll work with locales that use letters with diacritical marks. –
Richard Hansen
Feb 3 '12 at 18:58
$ string="A FEW WORDS"
$ echo "${string,}"
a FEW WORDS
$ echo "${string,,}"
a few words
$ echo "${string,,[AEIUO]}"
a FeW WoRDS
$ string="A Few Words"
$ declare -l string
$ string=$string; echo "$string"
a few words
To uppercase
$ string="a few words"
$ echo "${string^}"
A few words
$ echo "${string^^}"
A FEW WORDS
$ echo "${string^^[aeiou]}"
A fEw wOrds
$ string="A Few Words"
$ declare -u string
$ string=$string; echo "$string"
A FEW WORDS
Toggle (undocumented, but optionally configurable at compile time)
$ string="A Few Words"
$ echo "${string~~}"
a fEW wORDS
$ string="A FEW WORDS"
$ echo "${string~}"
a FEW WORDS
$ string="a few words"
$ echo "${string~}"
A few words
Capitalize (undocumented, but optionally configurable at compile time)
$ string="a few words"
$ declare -c string
$ string=$string
$ echo "$string"
A few words
Title case:
$ string="a few words"
$ string=($string)
$ string="${string[@]^}"
$ echo "$string"
A Few Words
$ declare -c string
$ string=(a few words)
$ echo "${string[@]}"
A Few Words
$ string="a FeW WOrdS"
$ string=${string,,}
$ string=${string~}
$ echo "$string"
To turn off a declare attribute, use + . For example, declare
+c string . This affects subsequent assignments and not the current value.
The declare options change the attribute of the variable, but not the contents.
The reassignments in my examples update the contents to show the changes.
Edit:
Added "toggle first character by word" ( ${var~} ) as suggested by ghostdog74
Quite bizzare, "^^" and ",," operators don't work on non-ASCII characters but "~~" does... So
string="łódź"; echo ${string~~} will return "ŁÓDŹ", but echo ${string^^}
returns "łóDź". Even in LC_ALL=pl_PL.utf-8 . That's using bash 4.2.24. –
Hubert Kario
Jul 12 '12 at 16:48
@HubertKario: That's weird. It's the same for me in Bash 4.0.33 with the same string in
en_US.UTF-8 . It's a bug and I've reported it. –
Dennis Williamson
Jul 12 '12 at 18:20
@HubertKario: Try echo "$string" | tr '[:lower:]' '[:upper:]' . It will probably
exhibit the same failure. So the problem is at least partly not Bash's. –
Dennis Williamson
Jul 13 '12 at 0:44
@RichardHansen: tr doesn't work for me for non-ACII characters. I do have correct
locale set and locale files generated. Have any idea what could I be doing wrong? –
Hubert Kario
Jul 12 '12 at 16:56
I strongly recommend the sed solution; I've been working in an environment that for
some reason doesn't have tr but I've yet to find a system without sed
, plus a lot of the time I want to do this I've just done something else in sed anyway
so can chain the commands together into a single (long) statement. –
Haravikk
Oct 19 '13 at 12:54
The bracket expressions should be quoted. In tr [A-Z] [a-z] A , the shell may perform
filename expansion if there are filenames consisting of a single letter or nullgob is set.
tr "[A-Z]" "[a-z]" A will behave properly. –
Dennis
Nov 6 '13 at 19:49
@CamiloMartin it's a BusyBox system where I'm having that problem, specifically Synology NASes,
but I've encountered it on a few other systems too. I've been doing a lot of cross-platform shell
scripting lately, and with the requirement that nothing extra be installed it makes things very
tricky! However I've yet to encounter a system without sed –
Haravikk
Jun 15 '14 at 10:51
Note that tr [A-Z] [a-z] is incorrect in almost all locales. for example, in the
en-US locale, A-Z is actually the interval AaBbCcDdEeFfGgHh...XxYyZ
. – fuz
Jan 31 '16 at 14:54
@JESii both work for me upper -> lower and lower-> upper. I'm using sed 4.2.2 and Bash 4.3.42(1)
on 64bit Debian Stretch. –
nettux443
Nov 20 '15 at 14:33
Hi, @nettux443... I just tried the bash operation again and it still fails for me with the error
message "bad substitution". I'm on OSX using homebrew's bash: GNU bash, version 4.3.42(1)-release
(x86_64-apple-darwin14.5.0) –
JESii
Nov 21 '15 at 17:34
Do not use! All of the examples which generate a script are extremely brittle; if the value
of a contains a single quote, you have not only broken behavior, but a serious security
problem. – tripleee
Jan 16 '16 at 11:45
I wonder if you didn't let some bashism in this script, as it's not portable on FreeBSD sh: ${1:$...}:
Bad substitution –
Dereckson
Nov 23 '14 at 19:52
I would like to take credit for the command I wish to share but the truth is I obtained it
for my own use from http://commandlinefu.com
. It has the advantage that if you cd to any directory within your own home folder
that is it will change all files and folders to lower case recursively please use with caution.
It is a brilliant command line fix and especially useful for those multitudes of albums you have
stored on your drive.
This didn't work for me for whatever reason, though it looks fine. I did get this to work as an
alternative though: find . -exec /bin/bash -c 'mv {} `tr [A-Z] [a-z] <<< {}`' \; –
John Rix
Jun 26 '13 at 15:58
For Bash versions earlier than 4.0, this version should be fastest (as it doesn't
fork/exec any commands):
function string.monolithic.tolower
{
local __word=$1
local __len=${#__word}
local __char
local __octal
local __decimal
local __result
for (( i=0; i<__len; i++ ))
do
__char=${__word:$i:1}
case "$__char" in
[A-Z] )
printf -v __decimal '%d' "'$__char"
printf -v __octal '%03o' $(( $__decimal ^ 0x20 ))
printf -v __char \\$__octal
;;
esac
__result+="$__char"
done
REPLY="$__result"
}
If using v4, this is
baked-in
. If not, here is a simple, widely applicable solution. Other answers (and comments) on this thread
were quite helpful in creating the code below.
# Like echo, but converts to lowercase
echolcase () {
tr [:upper:] [:lower:] <<< "${*}"
}
# Takes one arg by reference (var name) and makes it lowercase
lcase () {
eval "${1}"=\'$(echo ${!1//\'/"'\''"} | tr [:upper:] [:lower:] )\'
}
Notes:
Doing: a="Hi All" and then: lcase a will do the same thing as:
a=$( echolcase "Hi All" )
In the lcase function, using ${!1//\'/"'\''"} instead of ${!1}
allows this to work even when the string has quotes.
In spite of how old this question is and similar to
this answer by technosaurus
. I had a hard time finding a solution that was portable across most platforms (That I Use) as
well as older versions of bash. I have also been frustrated with arrays, functions and use of
prints, echos and temporary files to retrieve trivial variables. This works very well for me so
far I thought I would share. My main testing environments are:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
GNU bash, version 3.2.57(1)-release (sparc-sun-solaris2.10)
lcs="abcdefghijklmnopqrstuvwxyz"
ucs="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
input="Change Me To All Capitals"
for (( i=0; i<"${#input}"; i++ )) ; do :
for (( j=0; j<"${#lcs}"; j++ )) ; do :
if [[ "${input:$i:1}" == "${lcs:$j:1}" ]] ; then
input="${input/${input:$i:1}/${ucs:$j:1}}"
fi
done
done
Simple C-style for loop
to iterate through the strings. For the line below if you have not seen anything like this before
this is where
I learned this . In this case the line checks if the char ${input:$i:1} (lower case) exists
in input and if so replaces it with the given char ${ucs:$j:1} (upper case) and stores it back
into input.
Many answers using external programs, which is not really using Bash .
If you know you will have Bash4 available you should really just use the ${VAR,,}
notation (it is easy and cool). For Bash before 4 (My Mac still uses Bash 3.2 for example). I
used the corrected version of @ghostdog74 's answer to create a more portable version.
One you can call lowercase 'my STRING' and get a lowercase version. I read comments
about setting the result to a var, but that is not really portable in Bash , since
we can't return strings. Printing it is the best solution. Easy to capture with something like
var="$(lowercase $str)" .
How this works
The way this works is by getting the ASCII integer representation of each char with printf
and then adding 32 if upper-to->lower , or subtracting 32
if lower-to->upper . Then use printf again to convert the number back
to a char. From 'A' -to-> 'a' we have a difference of 32 chars.
Using printf to explain:
$ printf "%d\n" "'a"
97
$ printf "%d\n" "'A"
65
97 - 65 = 32
And this is the working version with examples.
Please note the comments in the code, as they explain a lot of stuff:
#!/bin/bash
# lowerupper.sh
# Prints the lowercase version of a char
lowercaseChar(){
case "$1" in
[A-Z])
n=$(printf "%d" "'$1")
n=$((n+32))
printf \\$(printf "%o" "$n")
;;
*)
printf "%s" "$1"
;;
esac
}
# Prints the lowercase version of a sequence of strings
lowercase() {
word="$@"
for((i=0;i<${#word};i++)); do
ch="${word:$i:1}"
lowercaseChar "$ch"
done
}
# Prints the uppercase version of a char
uppercaseChar(){
case "$1" in
[a-z])
n=$(printf "%d" "'$1")
n=$((n-32))
printf \\$(printf "%o" "$n")
;;
*)
printf "%s" "$1"
;;
esac
}
# Prints the uppercase version of a sequence of strings
uppercase() {
word="$@"
for((i=0;i<${#word};i++)); do
ch="${word:$i:1}"
uppercaseChar "$ch"
done
}
# The functions will not add a new line, so use echo or
# append it if you want a new line after printing
# Printing stuff directly
lowercase "I AM the Walrus!"$'\n'
uppercase "I AM the Walrus!"$'\n'
echo "----------"
# Printing a var
str="A StRing WITH mixed sTUFF!"
lowercase "$str"$'\n'
uppercase "$str"$'\n'
echo "----------"
# Not quoting the var should also work,
# since we use "$@" inside the functions
lowercase $str$'\n'
uppercase $str$'\n'
echo "----------"
# Assigning to a var
myLowerVar="$(lowercase $str)"
myUpperVar="$(uppercase $str)"
echo "myLowerVar: $myLowerVar"
echo "myUpperVar: $myUpperVar"
echo "----------"
# You can even do stuff like
if [[ 'option 2' = "$(lowercase 'OPTION 2')" ]]; then
echo "Fine! All the same!"
else
echo "Ops! Not the same!"
fi
exit 0
And the results after running this:
$ ./lowerupper.sh
i am the walrus!
I AM THE WALRUS!
----------
a string with mixed stuff!
A STRING WITH MIXED STUFF!
----------
a string with mixed stuff!
A STRING WITH MIXED STUFF!
----------
myLowerVar: a string with mixed stuff!
myUpperVar: A STRING WITH MIXED STUFF!
----------
Fine! All the same!
This should only work for ASCII characters though .
For me it is fine, since I know I will only pass ASCII chars to it.
I am using this for some case-insensitive CLI options, for example.
The here-document is great, but it's messing up your shell script's formatting. You want to
be able to indent for readability. Solution
Use <<- and then you can use tab characters (only!) at the beginning of lines to
indent this portion of your shell script.
$ cat myscript.sh
...
grep $1 <<-'EOF'
lots of data
can go here
it's indented with tabs
to match the script's indenting
but the leading tabs are
discarded when read
EOF
ls
...
$
Discussion
The hyphen just after the << is enough to tell bash to ignore the leading tab
characters. This is for tab characters only and not arbitrary white space. This is
especially important with the EOF or any other marker designation. If you have
spaces there, it will not recognize the EOF as your ending marker, and the "here"
data will continue through to the end of the file (swallowing the rest of your script).
Therefore, you may want to always left-justify the EOF (or other marker) just to
be safe, and let the formatting go on this one line.
The Bourne shell provides here documents to allow block of data to be passed to a process
through STDIN. The typical format for a here document is something similar to this:
command <<ARBITRARY_TAG
data to pass 1
data to pass 2
ARBITRARY_TAG
This will send the data between the ARBITRARY_TAG statements to the standard input of the
process. In order for this to work, you need to make sure that the data is not indented. If you
indent it for readability, you will get a syntax error similar to the following:
./test: line 12: syntax error: unexpected end of file
To allow your here documents to be indented, you can append a "-" to the end of the
redirection strings like so:
if [ "${STRING}" = "SOMETHING" ]
then
somecommand <<-EOF
this is a string1
this is a string2
this is a string3
EOF
fi
You will need to use tabs to indent the data, but that is a small price to pay for added
readability. Nice!
To enable automatic user logout, we will be using the TMOUT shell variable,
which terminates a user's login shell in case there is no activity for a given number of
seconds that you can specify.
To enable this globally (system-wide for all users), set the above variable in the
/etc/profile shell initialization file.
You may use spaces, parentheses and so forth, if you quote the expression:
$ let a='(5+2)*3'
For a full list of operators availabile, see help let or the manual.
Next, the actual arithmetic evaluation compound command syntax:
$ ((a=(5+2)*3))
This is equivalent to let , but we can also use it as a command , for
example in an if statement:
$ if (($a == 21)); then echo 'Blackjack!'; fi
Operators such as == , < , > and so on cause a comparison
to be performed, inside an arithmetic evaluation. If the comparison is "true" (for example,
10 > 2 is true in arithmetic -- but not in strings!) then the compound command
exits with status 0. If the comparison is false, it exits with status 1. This makes it suitable
for testing things in a script.
Although not a compound command, an arithmetic substitution (or arithmetic
expression ) syntax is also available:
$ echo "There are $(($rows * $columns)) cells"
Inside $((...)) is an arithmetic context , just like with ((...))
, meaning we do arithmetic (multiplying things) instead of string manipulations (concatenating
$rows , space, asterisk, space, $columns ). $((...)) is also
portable to the POSIX shell, while ((...)) is not.
Readers who are familiar with the C programming language might wish to know that
((...)) has many C-like features. Among them are the ternary operator:
$ ((abs = (a >= 0) ? a : -a))
and the use of an integer value as a truth value:
$ if ((flag)); then echo "uh oh, our flag is up"; fi
Note that we used variables inside ((...)) without prefixing them with $
-signs. This is a special syntactic shortcut that Bash allows inside arithmetic evaluations and
arithmetic expressions.
There is one final thing we must mention about ((flag)) . Because the inside of
((...)) is C-like, a variable (or expression) that evaluates to zero will be
considered false for the purposes of the arithmetic evaluation. Then, because the
evaluation is false, it will exit with a status of 1. Likewise, if the expression
inside ((...)) is non-zero , it will be considered true ; and since
the evaluation is true, it will exit with status 0. This is potentially very
confusing, even to experts, so you should take some time to think about this. Nevertheless,
when things are used the way they're intended, it makes sense in the end:
$ flag=0 # no error
$ while read line; do
> if [[ $line = *err* ]]; then flag=1; fi
> done < inputfile
$ if ((flag)); then echo "oh no"; fi
Option 1a: While loop: Single line at a time: Input redirection
#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do
echo $p
done < $filename
Option 1b: While loop: Single line at a time:
Open the file, read from a file descriptor (in this case file descriptor #4).
#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
echo $p
done
Option 2: For loop: Read file into single variable and parse.
This syntax will parse "lines" based on any white space between the tokens. This still works because
the given input file lines are single work tokens. If there were more than one token per line,
then this method would not work as well. Also, reading the full file into a single variable is
not a good strategy for large files.
#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
echo $line
done
This is no better than other answers, but is one more way to get the job done in a file without
spaces (see comments). I find that I often need one-liners to dig through lists in text files
without the extra step of using separate script files.
for word in $(cat peptides.txt); do echo $word; done
This format allows me to put it all in one command-line. Change the "echo $word" portion to
whatever you want and you can issue multiple commands separated by semicolons. The following example
uses the file's contents as arguments into two other scripts you may have written.
for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done
Or if you intend to use this like a stream editor (learn sed) you can dump the output to another
file as follows.
for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt
I've used these as written above because I have used text files where I've created them with
one word per line. (See comments) If you have spaces that you don't want splitting your words/lines,
it gets a little uglier, but the same command still works as follows:
OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS
This just tells the shell to split on newlines only, not spaces, then returns the environment
back to what it was previously. At this point, you may want to consider putting it all into a
shell script rather than squeezing it all into a single line, though.
A few more things not covered by other answers: Reading from a delimited file
# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
# process the fields
# if the line has less than three fields, the missing fields will be set to an empty string
# if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt
Reading from more than one file at a time
while read -u 3 -r line1 && read -u 4 -r line2; do
# process the lines
# note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt
Reading a whole file into an array (Bash version 4+)
readarray -t my_array < my_file
or
mapfile -t my_array < my_file
And then
for line in "${my_array[@]}"; do
# process the lines
done
#!/bin/bash
#
# Change the file name from "test" to desired input file
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
echo $x
done
$ cat /tmp/test.txt
Line 1
Line 2 has leading space
Line 3 followed by blank line
Line 5 (follows a blank line) and has trailing space
Line 6 has no ending CR
There are four elements that will alter the meaning of the file output read by many Bash solutions:
The blank line 4;
Leading or trailing spaces on two lines;
Maintaining the meaning of individual lines (i.e., each line is a record);
The line 6 not terminated with a CR.
If you want the text file line by line including blank lines and terminating lines without
CR, you must use a while loop and you must have an alternate test for the final line.
Here are the methods that may change the file (in comparison to what cat returns):
1) Lose the last line and leading and trailing spaces:
$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
(If you do while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
instead, you preserve the leading and trailing spaces but still lose the last line if it is not
terminated with CR)
2) Using process substitution with cat will reads the entire file in one gulp
and loses the meaning of individual lines:
$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
Line 2 has leading space
Line 3 followed by blank line
Line 5 (follows a blank line) and has trailing space
Line 6 has no ending CR'
(If you remove the " from $(cat /tmp/test.txt) you read the file
word by word rather than one gulp. Also probably not what is intended...)
The most robust and simplest way to read a file line-by-line and preserve all spacing is:
$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
' Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space '
'Line 6 has no ending CR'
If you want to strip leading and trading spaces, remove the IFS= part:
$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'
(A text file without a terminating \n , while fairly common, is considered broken
under POSIX. If you can count on the trailing \n you do not need || [[ -n $line
]] in the while loop.)
Here is my real life example how to loop lines of another program output, check for substrings,
drop double quotes from variable, use that variable outside of the loop. I guess quite many is
asking these questions sooner or later.
##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
echo ParseFPS $line
FPS=parse
fi
if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
echo ParseFPS $line
FPS=${line##*=}
FPS="${FPS%\"}"
FPS="${FPS#\"}"
fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then
echo ParseFPS Unknown frame rate
fi
echo Found $FPS
Declare variable outside of the loop, set value and use it outside of loop requires done
<<< "$(...)" syntax. Application need to be run within a context of current console. Quotes
around the command keeps newlines of output stream.
Loop match for substrings then reads name=value pair, splits right-side part of last
= character, drops first quote, drops last quote, we have a clean value to be used elsewhere.
Is there any directory bookmarking utility for bash to allow move around faster on the command
line?
UPDATE
Thanks guys for the feedback however I created my own simple shell script (feel free to modify/expand
it)
function cdb() {
USAGE="Usage: cdb [-c|-g|-d|-l] [bookmark]" ;
if [ ! -e ~/.cd_bookmarks ] ; then
mkdir ~/.cd_bookmarks
fi
case $1 in
# create bookmark
-c) shift
if [ ! -f ~/.cd_bookmarks/$1 ] ; then
echo "cd `pwd`" > ~/.cd_bookmarks/"$1" ;
else
echo "Try again! Looks like there is already a bookmark '$1'"
fi
;;
# goto bookmark
-g) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
source ~/.cd_bookmarks/"$1"
else
echo "Mmm...looks like your bookmark has spontaneously combusted. What I mean to say is that your bookmark does not exist." ;
fi
;;
# delete bookmark
-d) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
rm ~/.cd_bookmarks/"$1" ;
else
echo "Oops, forgot to specify the bookmark" ;
fi
;;
# list bookmarks
-l) shift
ls -l ~/.cd_bookmarks/ ;
;;
*) echo "$USAGE" ;
;;
esac
}
INSTALL
1./ create a file ~/.cdb and copy the above script into it.
A colon-separated list of search paths available to the cd command, similar in function to
the $PATH variable for binaries. The $CDPATH variable may be set in the local ~/.bashrc file.
ash$ cd bash-doc
bash: cd: bash-doc: No such file or directory
bash$ CDPATH=/usr/share/doc
bash$ cd bash-doc
/usr/share/doc/bash-doc
bash$ echo $PWD
/usr/share/doc/bash-doc
and
cd -
It's the command-line equivalent of the back button (takes you to the previous directory you
were in).
It primarily allows you to "fuzzy-find" files in a number of ways, but it also allows to feed
arbitrary text data to it and filter this data. So, the shortcuts idea is simple: all we need
is to maintain a file with paths (which are shortcuts), and fuzzy-filter this file. Here's how
it looks: we type cdg command (from "cd global", if you like), get a list of our
bookmarks, pick the needed one in just a few keystrokes, and press Enter. Working directory is
changed to the picked item:
It is extremely fast and convenient: usually I just type 3-4 letters of the needed item, and
all others are already filtered out. Additionally, of course we can move through list with arrow
keys or with vim-like keybindings Ctrl+j / Ctrl+k .
It is possible to use it for GUI applications as well (via xterm): I use that for my GUI file
manager Double Commander . I have
plans to write an article about this use case, too.
Inspired by the question and answers here, I added the lines below to my ~/.bashrc
file.
With this you have a favdir command (function) to manage your favorites and a
autocompletion function to select an item from these favorites.
# ---------
# Favorites
# ---------
__favdirs_storage=~/.favdirs
__favdirs=( "$HOME" )
containsElement () {
local e
for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
function favdirs() {
local cur
local IFS
local GLOBIGNORE
case $1 in
list)
echo "favorite folders ..."
printf -- ' - %s\n' "${__favdirs[@]}"
;;
load)
if [[ ! -e $__favdirs_storage ]] ; then
favdirs save
fi
# mapfile requires bash 4 / my OS-X bash vers. is 3.2.53 (from 2007 !!?!).
# mapfile -t __favdirs < $__favdirs_storage
IFS=$'\r\n' GLOBIGNORE='*' __favdirs=($(< $__favdirs_storage))
;;
save)
printf -- '%s\n' "${__favdirs[@]}" > $__favdirs_storage
;;
add)
cur=${2-$(pwd)}
favdirs load
if containsElement "$cur" "${__favdirs[@]}" ; then
echo "'$cur' allready exists in favorites"
else
__favdirs+=( "$cur" )
favdirs save
echo "'$cur' added to favorites"
fi
;;
del)
cur=${2-$(pwd)}
favdirs load
local i=0
for fav in ${__favdirs[@]}; do
if [ "$fav" = "$cur" ]; then
echo "delete '$cur' from favorites"
unset __favdirs[$i]
favdirs save
break
fi
let i++
done
;;
*)
echo "Manage favorite folders."
echo ""
echo "usage: favdirs [ list | load | save | add | del ]"
echo ""
echo " list : list favorite folders"
echo " load : load favorite folders from $__favdirs_storage"
echo " save : save favorite directories to $__favdirs_storage"
echo " add : add directory to favorites [default pwd $(pwd)]."
echo " del : delete directory from favorites [default pwd $(pwd)]."
esac
} && favdirs load
function __favdirs_compl_command() {
COMPREPLY=( $( compgen -W "list load save add del" -- ${COMP_WORDS[COMP_CWORD]}))
} && complete -o default -F __favdirs_compl_command favdirs
function __favdirs_compl() {
local IFS=$'\n'
COMPREPLY=( $( compgen -W "${__favdirs[*]}" -- ${COMP_WORDS[COMP_CWORD]}))
}
alias _cd='cd'
complete -F __favdirs_compl _cd
Within the last two lines, an alias to change the current directory (with autocompletion) is
created. With this alias ( _cd ) you are able to change to one of your favorite directories.
May you wan't to change this alias to something which fits your needs .
With the function favdirs you can manage your favorites (see usage).
$ favdirs
Manage favorite folders.
usage: favdirs [ list | load | save | add | del ]
list : list favorite folders
load : load favorite folders from ~/.favdirs
save : save favorite directories to ~/.favdirs
add : add directory to favorites [default pwd /tmp ].
del : delete directory from favorites [default pwd /tmp ].
@getmizanur I used your cdb script. I enhanced it slightly by adding bookmarks tab completion.
Here's my version of your cdb script.
_cdb()
{
local _script_commands=$(ls -1 ~/.cd_bookmarks/)
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -W "${_script_commands}" -- $cur) )
}
complete -F _cdb cdb
function cdb() {
local USAGE="Usage: cdb [-h|-c|-d|-g|-l|-s] [bookmark]\n
\t[-h or no args] - prints usage help\n
\t[-c bookmark] - create bookmark\n
\t[-d bookmark] - delete bookmark\n
\t[-g bookmark] - goto bookmark\n
\t[-l] - list bookmarks\n
\t[-s bookmark] - show bookmark location\n
\t[bookmark] - same as [-g bookmark]\n
Press tab for bookmark completion.\n"
if [ ! -e ~/.cd_bookmarks ] ; then
mkdir ~/.cd_bookmarks
fi
case $1 in
# create bookmark
-c) shift
if [ ! -f ~/.cd_bookmarks/$1 ] ; then
echo "cd `pwd`" > ~/.cd_bookmarks/"$1"
complete -F _cdb cdb
else
echo "Try again! Looks like there is already a bookmark '$1'"
fi
;;
# goto bookmark
-g) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
source ~/.cd_bookmarks/"$1"
else
echo "Mmm...looks like your bookmark has spontaneously combusted. What I mean to say is that your bookmark does not exist." ;
fi
;;
# show bookmark
-s) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
cat ~/.cd_bookmarks/"$1"
else
echo "Mmm...looks like your bookmark has spontaneously combusted. What I mean to say is that your bookmark does not exist." ;
fi
;;
# delete bookmark
-d) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
rm ~/.cd_bookmarks/"$1" ;
else
echo "Oops, forgot to specify the bookmark" ;
fi
;;
# list bookmarks
-l) shift
ls -1 ~/.cd_bookmarks/ ;
;;
-h) echo -e $USAGE ;
;;
# goto bookmark by default
*)
if [ -z "$1" ] ; then
echo -e $USAGE
elif [ -f ~/.cd_bookmarks/$1 ] ; then
source ~/.cd_bookmarks/"$1"
else
echo "Mmm...looks like your bookmark has spontaneously combusted. What I mean to say is that your bookmark does not exist." ;
fi
;;
esac
}
Anc stands for anchor, but anc's anchors are really just bookmarks.
It's designed for ease of use and there're multiple ways of navigating, either by giving a
text pattern, using numbers, interactively, by going back, or using [TAB] completion.
I'm actively working on it and open to input on how to make it better.
Allow me to paste the examples from anc's github page here:
# make the current directory the default anchor:
$ anc s
# go to /etc, then /, then /usr/local and then back to the default anchor:
$ cd /etc; cd ..; cd usr/local; anc
# go back to /usr/local :
$ anc b
# add another anchor:
$ anc a $HOME/test
# view the list of anchors (the default one has the asterisk):
$ anc l
(0) /path/to/first/anchor *
(1) /home/usr/test
# jump to the anchor we just added:
# by using its anchor number
$ anc 1
# or by jumping to the last anchor in the list
$ anc -1
# add multiple anchors:
$ anc a $HOME/projects/first $HOME/projects/second $HOME/documents/first
# use text matching to jump to $HOME/projects/first
$ anc pro fir
# use text matching to jump to $HOME/documents/first
$ anc doc fir
# add anchor and jump to it using an absolute path
$ anc /etc
# is the same as
$ anc a /etc; anc -1
# add anchor and jump to it using a relative path
$ anc ./X11 #note that "./" is required for relative paths
# is the same as
$ anc a X11; anc -1
# using wildcards you can add many anchors at once
$ anc a $HOME/projects/*
# use shell completion to see a list of matching anchors
# and select the one you want to jump to directly
$ anc pro[TAB]
Bashmarks
is an amazingly simple and intuitive utility. In short, after installation, the usage is:
s <bookmark_name> - Saves the current directory as "bookmark_name"
g <bookmark_name> - Goes (cd) to the directory associated with "bookmark_name"
p <bookmark_name> - Prints the directory associated with "bookmark_name"
d <bookmark_name> - Deletes the bookmark
l - Lists all available bookmarks
,
For short term shortcuts, I have a the following in my respective init script (Sorry. I can't
find the source right now and didn't bother then):
function b() {
alias $1="cd `pwd -P`"
}
Usage:
In any directory that you want to bookmark type
b THEDIR # <THEDIR> being the name of your 'bookmark'
It will create an alias to cd (back) to here.
To return to a 'bookmarked' dir type
THEDIR
It will run the stored alias and cd back there.
Caution: Use only if you understand that this might override existing shell aliases
and what that means.
I read
here that the purpose of export in a shell is to make the variable available
to sub-processes started from the shell.
However, I have also read
here and here that
"Processes inherit their environment from their parent (the process which started them)."
If this is the case, why do we need export ? What am I missing?
Are shell variables not part of the environment by default? What is the difference?
Your assumption is that all shell variables are in the environment . This is incorrect.
The export command is what defines a name to be in the environment at all. Thus:
a=1
b=2
export b
results in the current shell knowing that $a expands to 1 and $b
to 2, but subprocesses will not know anything about a because it is not part of the
environment (even in the current shell).
Some useful tools:
set : Useful for viewing the current shell's parameters, exported-or-not
set -k : Sets assigned args in the environment. Consider f() {
set -k; env; }; f a=1
export : Tells the shell to put a name in the environment. Export and assignment
are two entirely different operations.
env : As an external command, env can only tell you about the
inherited environment, thus, it's useful for sanity checking.
env -i : Useful for clearing the environment before starting a subprocess.
Alternatives to export :
name=val command # Assignment before command exports that name to the command.
declare/local -x name # Exports name, particularly useful in shell functions
when you want to avoid exposing the name to outside scope.
====
There's a difference between shell variables and environment variables. If you define a shell
variable without export ing it, it is not added to the processes environment and thus
not inherited to its children.
Using export you tell the shell to add the shell variable to the environment. You
can test this using printenv (which just prints its environment to stdout, since it's a child-process you see the effect of export ing variables):
I am using startx to start the graphical environment. I have a very simple
.xinitrc which I will add things to as I set up the environment, but for now it
is as follows:
catwm
&
# Just a basic window manager, for testing.
xterm
The reason I background the WM and foreground terminal and not the other way around as often
is done, is because I would like to be able to come back to the virtual text console after
typing exit in xterm . This appears to work as described.
The problem is that the PS1 variable that currently is set to my preference
in /etc/profile.d/user.sh (which is sourced from /etc/profile supplied
by distro), does not appear to propagate to the environment of the xterm mentioned
above. The relevant process tree is as follows:
\_
bash
\_ xinit
home
user
/.
xinitrc
--
etc
X11
xinit
xserverrc
auth
tmp
serverauth
ggJna3I0vx
\_
usr
bin
nolisten tcp
auth
tmp
serverauth
ggJna3I0vx vt1
\_ sh
home
user
/.
xinitrc
\_
home
user
catwm
\_ xterm
\_ bash
The shell started by xterm appears to be interactive, the shell executing
.xinitrc however is not. I am ok with both, the assumptions about interactivity
seem to be perfectly valid, but now I have a non-interactive shell that spawns an interactive
shell indirectly, and the interactive shell has no chance to automatically inherit the prompt,
because the prompt was unset or otherwise made unavailable higher up the process tree.
Commands env and export list only variables which are exported.
$PS1 is usually not exported. Try echo $PS1 in your shell to see
actual value of $PS1 .
Non-interactive shells usually do not have $PS1 . Non-interactive bash
explicitly unsets $PS1 . 1
You can check if bash is interactive by echo $- . If the output contains
i then it is interactive. You can explicitly start interactive shell by using
the option on the command line: bash -i . Shell started with -c is
not interactive.
The /etc/profile script is read for a login shell. You can start the shell
as a login shell by: bash -l .
With bash shell the scripts /etc/bash.bashrc and ~/.bashrc
are usually used to set $PS1 . Those scripts are sourced when interactive non-login
shell is started. It is your case in the xterm .
Start the shell inside xterm as a login shell bash -l . Check
if /etc/profile and ~/.profile do not contain code which should
be executed only after login. Maybe slight modifications of the scripts will be needed.
Use a different shell. For example dash does not unset $PS1
. You can use such a shell just as the non-interactive shell which will run the scripts
up to xterm .
Give up the strict POSIX compliance and use the bash-standard place for setting
$PS1 : /etc/bash.bashrc or ~/.bashrc .
Give up the strict POSIX compliance and source your own startup script like: bash
--rcfile <(echo "PS1=$PS1save") -i
Start the intermediate shells from startx till xterm as interactive
shells ( bash -i ). Unfortunately this can have some side-effect and I would
not do this.
I am specifically avoiding to set PS1 in .bashrc or
/etc/bash.bashrc (which is executed as well), to retain POSIX shell compatibility.
These do not set or unset PS1 . PS1 is set in /etc/profile.d/user.sh
, which is sourced by /etc/profile . Indeed, this file is only executed
for login shells, however I do export PS1 from /etc/profile.d/user.sh
exactly because I want propagation of my preferred value down the process tree. So
it shouldn't matter which subshells are login and/or interactive ones then, should
it? – amn
Oct 21 '13 at 11:32
It seems that bash removes the PS1 variable. What exactly
do you want to achieve by "POSIX shell compatibility"? Do you want to be able to replace
bash by a different POSIX-compliant shell and retain the same functionality?
Based on my tests bash removes PS1 when it is started as
non-interactive. I think of two simple solutions: 1. start the shell as a login
shell with the -l option (attention for actions in the startup scripts
which should be started only at login) 2. start the intermediate shells as
interactive with the -i option. –
pabouk
Oct 21 '13 at 12:00
I try to follow interfaces and specifications, not implementations - hence POSIX
compatibility. That's important (to me). I already have one login shell - the one
started by /usr/bin/login . I understand that a non-interactive shell
doesn't need prompt, but unsetting a variable is too much - I need the prompt in an
interactive shell (spawned and used by xterm ) later on. What am I doing
wrong? I guess most people set their prompt in .bashrc which is sourced
by bash anyway, and so the prompt survives. I try to avoid .bashrc however.
– amn
Oct 22 '13 at 12:12
The Learning Bash Book mention that a subshell will inherit only environment variabels and
file descriptors , ...etc and that it will not inherit variables that are not exported of
$ var=15
$ (echo $var)
15
$ ./file # this file include the same command echo $var
$
As i know the shell will create two subshells for () case and for ./file, but why in ()
case the subshell identified the var variable although it is not exported and in the ./file
case it did not identify it ?
...
I tried to use strace to figure out how this happens and surprisingly i found that bash
will use the same arguments for the clone system call so this means that the both forked
process in () and ./file should have the same process address space of the parent, so why in
() case the variable is visible to the subshell and the same does not happen for ./file case
although the same arguments is based with clone system call ?
The subshell created using parentheses does not use an execve()
call for the new process, the calling of the script does. At this point the variables
from the parent shell are handled differently: The execve() passes a deliberate
set of variables (the script-calling case) while not calling execve() (the
parentheses case) leaves the complete set of variables intact.
Your probing using strace should have shown exactly that difference; if you
did not see it, I can only assume that you made one of several possible mistakes. I will just
strip down what I did to show the difference, then you can decide for yourself where your
error was.
The solution for this mystery is that subshells inherit everything from the parent shell
including all shell variables because they are simply called with fork or clone so they share
the same memory space with the parent shell , that's why this will work
$ var=15
$ (echo $var)
15
But in the ./file , the subshell will be later followed by exec or execv system call which
will clear all the previous parent variables but we still have the environment variables you
can check this out using strace using -f to monitor the child subshell and you will find that
there is a call to execv
When interacting with your server through a shell session, there are many pieces of
information that your shell compiles to determine its behavior and access to resources. Some of
these settings are contained within configuration settings and others are determined by user
input.
One way that the shell keeps track of all of these settings and details is through an area
it maintains called the environment . The environment is an area that the shell builds every
time that it starts a session that contains variables that define system properties.
In this guide, we will discuss how to interact with the environment and read or set
environmental and shell variables interactively and through configuration files. We will be
using an Ubuntu 12.04 VPS as an example, but these details should be relevant on any Linux
system.
How the Environment and Environmental Variables Work
Every time a shell session spawns, a process takes place to gather and compile information
that should be available to the shell process and its child processes. It obtains the data for
these settings from a variety of different files and settings on the system.
Basically the environment provides a medium through which the shell process can get or set
settings and, in turn, pass these on to its child processes.
The environment is implemented as strings that represent key-value pairs. If multiple values
are passed, they are typically separated by colon (:) characters. Each pair will generally will
look something like this:
KEY
value1
value2:...
If the value contains significant white-space, quotations are used:
KEY
="
value with spaces
"
The keys in these scenarios are variables. They can be one of two types, environmental
variables or shell variables.
Environmental variables are variables that are defined for the current shell and are
inherited by any child shells or processes. Environmental variables are used to pass
information into processes that are spawned from the shell.
Shell variables are variables that are contained exclusively within the shell in which they
were set or defined. They are often used to keep track of ephemeral data, like the current
working directory.
By convention, these types of variables are usually defined using all capital letters. This
helps users distinguish environmental variables within other contexts.
Printing Shell and
Environmental Variables
Each shell session keeps track of its own shell and environmental variables. We can access
these in a few different ways.
We can see a list of all of our environmental variables by using the env or
printenv commands. In their default state, they should function exactly the
same:
This is fairly typical of the output of both printenv and env .
The difference between the two commands is only apparent in their more specific functionality.
For instance, with printenv , you can requests the values of individual
variables:
printenv SHELL
/bin/bash
On the other hand, env let's you modify the environment that programs run in by
passing a set of variable definitions into a command like this:
Since, as we learned above, child processes typically inherit the environmental variables of
the parent process, this gives you the opportunity to override values or add additional
variables for the child.
As you can see from the output of our printenv command, there are quite a few
environmental variables set up through our system files and processes without our input.
These show the environmental variables, but how do we see shell variables?
The set command can be used for this. If we type set without any
additional parameters, we will get a list of all shell variables, environmental variables,
local variables, and shell functions:
This is usually a huge list. You probably want to pipe it into a pager program to deal with
the amount of output easily:
set | less
The amount of additional information that we receive back is a bit overwhelming. We probably
do not need to know all of the bash functions that are defined, for instance.
We can clean up the output by specifying that set should operate in POSIX mode,
which won't print the shell functions. We can execute this in a sub-shell so that it does not
change our current environment:
(set -o posix; set)
This will list all of the environmental and shell variables that are defined.
We can attempt to compare this output with the output of the env or
printenv commands to try to get a list of only shell variables, but this will be
imperfect due to the different ways that these commands output information:
comm -23 <(set -o posix; set | sort) <(env | sort)
This will likely still include a few environmental variables, due to the fact that the
set command outputs quoted values, while the printenv and
env commands do not quote the values of strings.
This should still give you a good idea of the environmental and shell variables that are set
in your session.
These variables are used for all sorts of things. They provide an alternative way of setting
persistent values for the session between processes, without writing changes to a
file.
Common Environmental and Shell Variables
Some environmental and shell variables are very useful and are referenced fairly often.
Here are some common environmental variables that you will come across:
SHELL : This describes the shell that will be interpreting any commands you type in. In
most cases, this will be bash by default, but other values can be set if you prefer other
options.
TERM : This specifies the type of terminal to emulate when running the shell. Different
hardware terminals can be emulated for different operating requirements. You usually won't
need to worry about this though.
USER : The current logged in user.
PWD : The current working directory.
OLDPWD : The previous working directory. This is kept by the shell in order to switch
back to your previous directory by running cd - .
LS_COLORS : This defines color codes that are used to optionally add colored output to
the ls command. This is used to distinguish different file types and provide
more info to the user at a glance.
MAIL : The path to the current user's mailbox.
PATH : A list of directories that the system will check when looking for commands. When a
user types in a command, the system will check directories in this order for the
executable.
LANG : The current language and localization settings, including character encoding.
HOME : The current user's home directory.
: The most recent previously executed command.
In addition to these environmental variables, some shell variables that you'll often see
are:
BASHOPTS : The list of options that were used when bash was executed. This can be useful
for finding out if the shell environment will operate in the way you want it to.
BASH_VERSION : The version of bash being executed, in human-readable form.
BASH_VERSINFO : The version of bash, in machine-readable output.
COLUMNS : The number of columns wide that are being used to draw output on the
screen.
DIRSTACK : The stack of directories that are available with the pushd and
popd commands.
HISTFILESIZE : Number of lines of command history stored to a file.
HISTSIZE : Number of lines of command history allowed in memory.
HOSTNAME : The hostname of the computer at this time.
IFS : The internal field separator to separate input on the command line. By default,
this is a space.
PS1 : The primary command prompt definition. This is used to define what your prompt
looks like when you start a shell session. The PS2 is used to declare secondary
prompts for when a command spans multiple lines.
SHELLOPTS : Shell options that can be set with the set option.
UID : The UID of the current user.
Setting Shell and Environmental Variables
To better understand the difference between shell and environmental variables, and to
introduce the syntax for setting these variables, we will do a small
demonstration.
Creating Shell Variables
We will begin by defining a shell variable within our current session. This is easy to
accomplish; we only need to specify a name and a value. We'll adhere to the convention of
keeping all caps for the variable name, and set it to a simple string.
TEST_VAR='Hello World!'
Here, we've used quotations since the value of our variable contains a space. Furthermore,
we've used single quotes because the exclamation point is a special character in the bash shell
that normally expands to the bash history if it is not escaped or put into single quotes.
We now have a shell variable. This variable is available in our current session, but will
not be passed down to child processes.
We can see this by grepping for our new variable within the set output:
set | grep TEST_VAR
TEST_VAR='Hello World!'
We can verify that this is not an environmental variable by trying the same thing with
printenv :
printenv | grep TEST_VAR
No out should be returned.
Let's take this as an opportunity to demonstrate a way of accessing the value of any shell
or environmental variable.
echo $TEST_VAR
Hello World!
As you can see, reference the value of a variable by preceding it with a $
sign. The shell takes this to mean that it should substitute the value of the variable when it
comes across this.
So now we have a shell variable. It shouldn't be passed on to any child processes. We can
spawn a new bash shell from within our current one to demonstrate:
bash
echo $TEST_VAR
If we type bash to spawn a child shell, and then try to access the contents of
the variable, nothing will be returned. This is what we expected.
Get back to our original shell by typing exit :
exit
Creating Environmental Variables
Now, let's turn our shell variable into an environmental variable. We can do this by
exporting the variable. The command to do so is appropriately named:
export TEST_VAR
This will change our variable into an environmental variable. We can check this by checking
our environmental listing again:
printenv | grep TEST_VAR
TEST_VAR=Hello World!
This time, our variable shows up. Let's try our experiment with our child shell again:
bash
echo $TEST_VAR
Hello World!
Great! Our child shell has received the variable set by its parent. Before we exit this
child shell, let's try to export another variable. We can set environmental variables in a
single step like this:
export NEW_VAR="Testing export"
Test that it's exported as an environmental variable:
printenv | grep NEW_VAR
NEW_VAR=Testing export
Now, let's exit back into our original shell:
exit
Let's see if our new variable is available:
echo $NEW_VAR
Nothing is returned.
This is because environmental variables are only passed to child processes. There isn't a
built-in way of setting environmental variables of the parent shell. This is good in most cases
and prevents programs from affecting the operating environment from which they were called.
The NEW_VAR variable was set as an environmental variable in our child shell.
This variable would be available to itself and any of its child shells and processes. When we
exited back into our main shell, that environment was destroyed.
Demoting and Unsetting
Variables
We still have our TEST_VAR variable defined as an environmental variable. We
can change it back into a shell variable by typing:
export -n TEST_VAR
It is no longer an environmental variable:
printenv | grep TEST_VAR
However, it is still a shell variable:
set | grep TEST_VAR
TEST_VAR='Hello World!'
If we want to completely unset a variable, either shell or environmental, we can do so with
the unset command:
unset TEST_VAR
We can verify that it is no longer set:
echo $TEST_VAR
Nothing is returned because the variable has been unset.
Setting Environmental
Variables at Login
We've already mentioned that many programs use environmental variables to decide the
specifics of how to operate. We do not want to have to set important variables up every time we
start a new shell session, and we have already seen how many variables are already set upon
login, so how do we make and define variables automatically?
This is actually a more complex problem than it initially seems, due to the numerous
configuration files that the bash shell reads depending on how it is started.
The
Difference between Login, Non-Login, Interactive, and Non-Interactive Shell Sessions
The bash shell reads different configuration files depending on how the session is
started.
One distinction between different sessions is whether the shell is being spawned as a
"login" or "non-login" session.
A login shell is a shell session that begins by authenticating the user. If you are signing
into a terminal session or through SSH and authenticate, your shell session will be set as a
"login" shell.
If you start a new shell session from within your authenticated session, like we did by
calling the bash command from the terminal, a non-login shell session is started.
You were were not asked for your authentication details when you started your child shell.
Another distinction that can be made is whether a shell session is interactive, or
non-interactive.
An interactive shell session is a shell session that is attached to a terminal. A
non-interactive shell session is one is not attached to a terminal session.
So each shell session is classified as either login or non-login and interactive or
non-interactive.
A normal session that begins with SSH is usually an interactive login shell. A script run
from the command line is usually run in a non-interactive, non-login shell. A terminal session
can be any combination of these two properties.
Whether a shell session is classified as a login or non-login shell has implications on
which files are read to initialize the shell session.
A session started as a login session will read configuration details from the
/etc/profile file first. It will then look for the first login shell configuration
file in the user's home directory to get user-specific configuration details.
It reads the first file that it can find out of ~/.bash_profile ,
~/.bash_login , and ~/.profile and does not read any further
files.
In contrast, a session defined as a non-login shell will read /etc/bash.bashrc
and then the user-specific ~/.bashrc file to build its environment.
Non-interactive shells read the environmental variable called BASH_ENV and read
the file specified to define the new environment.
Implementing Environmental
Variables
As you can see, there are a variety of different files that we would usually need to look at
for placing our settings.
This provides a lot of flexibility that can help in specific situations where we want
certain settings in a login shell, and other settings in a non-login shell. However, most of
the time we will want the same settings in both situations.
Fortunately, most Linux distributions configure the login configuration files to source the
non-login configuration files. This means that you can define environmental variables that you
want in both inside the non-login configuration files. They will then be read in both
scenarios.
We will usually be setting user-specific environmental variables, and we usually will want
our settings to be available in both login and non-login shells. This means that the place to
define these variables is in the ~/.bashrc file.
Open this file now:
nano ~/.bashrc
This will most likely contain quite a bit of data already. Most of the definitions here are
for setting bash options, which are unrelated to environmental variables. You can set
environmental variables just like you would from the command line:
export VARNAME=value
We can then save and close the file. The next time you start a shell session, your
environmental variable declaration will be read and passed on to the shell environment. You can
force your current session to read the file now by typing:
source ~/.bashrc
If you need to set system-wide variables, you may want to think about adding them to
/etc/profile , /etc/bash.bashrc , or /etc/environment
.
Conclusion
Environmental and shell variables are always present in your shell sessions and can be very
useful. They are an interesting way for a parent process to set configuration details for its
children, and are a way of setting options outside of files.
This has many advantages in specific situations. For instance, some deployment mechanisms
rely on environmental variables to configure authentication information. This is useful because
it does not require keeping these in files that may be seen by outside parties.
There are plenty of other, more mundane, but more common scenarios where you will need to
read or alter the environment of your system. These tools and techniques should give you a good
foundation for making these changes and using them correctly.
I've used a number of different *nix-based systems of the years, and it seems like every flavor
of Bash I use has a different algorithm for deciding which startup scripts to run. For the purposes
of tasks like setting up environment variables and aliases and printing startup messages (e.g.
MOTDs), which startup script is the appropriate place to do these?
What's the difference between putting things in .bashrc , .bash_profile
, and .environment ? I've also seen other files such as .login ,
.bash_login , and .profile ; are these ever relevant? What are the differences
in which ones get run when logging in physically, logging in remotely via ssh, and opening a new
terminal window? Are there any significant differences across platforms (including Mac OS X (and
its Terminal.app) and Cygwin Bash)?
The main difference with shell config files is that some are only read by "login" shells (eg.
when you login from another host, or login at the text console of a local unix machine). these
are the ones called, say, .login or .profile or .zlogin
(depending on which shell you're using).
Then you have config files that are read by "interactive" shells (as in, ones connected to
a terminal (or pseudo-terminal in the case of, say, a terminal emulator running under a windowing
system). these are the ones with names like .bashrc , .tcshrc ,
.zshrc , etc.
bash complicates this in that .bashrc is only read by a shell that's
both interactive and non-login , so you'll find most people end up telling their
.bash_profile to also read .bashrc with something like
[[ -r ~/.bashrc ]] && . ~/.bashrc
Other shells behave differently - eg with zsh , .zshrc is always
read for an interactive shell, whether it's a login one or not.
The manual page for bash explains the circumstances under which each file is read. Yes, behaviour
is generally consistent between machines.
.profile is simply the login script filename originally used by /bin/sh
. bash , being generally backwards-compatible with /bin/sh , will read
.profile if one exists.
Login shells are the ones that are the one you login (so, they are not executed when merely
starting up xterm, for example). There are other ways to login. For example using an X display
manager. Those have other ways to read and export environment variables at login time.
Also read the INVOCATION chapter in the manual. It says "The following paragraphs
describe how bash executes its startup files." , i think that's a spot-on :) It explains
what an "interactive" shell is too.
Bash does not know about .environment . I suspect that's a file of your distribution,
to set environment variables independent of the shell that you drive.
Classically, ~/.profile is used by Bourne Shell, and is probably supported by Bash
as a legacy measure. Again, ~/.login and ~/.cshrc were used by C Shell
- I'm not sure that Bash uses them at all.
The ~/.bash_profile would be used once, at login. The ~/.bashrc script
is read every time a shell is started. This is analogous to /.cshrc for C Shell.
One consequence is that stuff in ~/.bashrc should be as lightweight (minimal)
as possible to reduce the overhead when starting a non-login shell.
I believe the ~/.environment file is a compatibility file for Korn Shell.
I found information about .bashrc and .bash_profile
here to
sum it up:
.bash_profile is executed when you login. Stuff you put in there might be your PATH and
other important environment variables.
.bashrc is used for non login shells. I'm not sure what that means. I know that RedHat executes
it everytime you start another shell (su to this user or simply calling bash again) You might
want to put aliases in there but again I am not sure what that means. I simply ignore it myself.
.profile is the equivalent of .bash_profile for the root. I think the name is changed to
let other shells (csh, sh, tcsh) use it as well. (you don't need one as a user)
There is also .bash_logout wich executes at, yeah good guess...logout. You might want to
stop deamons or even make a little housekeeping . You can also add "clear" there if you want
to clear the screen when you log out.
Also there is a complete follow up on each of the configurations files
here
These are probably even distro.-dependant, not all distros choose to have each configuraton
with them and some have even more. But when they have the same name, they usualy include the same
content.
According to
Josh
Staiger , Mac OS X's Terminal.app actually runs a login shell rather than a non-login shell
by default for each new terminal window, calling .bash_profile instead of .bashrc.
He recommends:
Most of the time you don't want to maintain two separate config files for login and non-login
shells ! when you set a PATH, you want it to apply to both. You can fix this by sourcing .bashrc
from your .bash_profile file, then putting PATH and common settings in .bashrc.
To do this, add the following lines to .bash_profile:
if ~/.bashrc ]; then
source ~/.bashrc
fi
Now when you login to your machine from a console .bashrc will be called.
I have used Debian-family distros which appear to execute .profile , but not
.bash_profile , whereas RHEL derivatives execute .bash_profile before
.profile .
It seems to be a mess when you have to set up environment variables to work in any Linux OS.
I consistently have more than one terminal open. Anywhere from two to ten, doing various bits
and bobs. Now let's say I restart and open up another set of terminals. Some remember certain
things, some forget.
I want a history that:
Remembers everything from every terminal
Is instantly accessible from every terminal (eg if I
ls
in one, switch to
another already-running terminal and then press up,
ls
shows up)
Doesn't forget command if there are spaces at the front of the command.
Anything I can do to make bash work more like that?
# Avoid duplicates
export HISTCONTROL=ignoredups:erasedups
# When the shell exits, append to the history file instead of overwriting it
shopt -s histappend
# After each command, append to the history file and reread it
export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
export HISTCONTROL=ignoredups:erasedups # no duplicate entries
export HISTSIZE=100000 # big big history
export HISTFILESIZE=100000 # big big history
shopt -s histappend # append to history, don't overwrite it
# Save and reload the history after each command finishes
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"
Tested with bash 3.2.17 on Mac OS X 10.5, bash 4.1.7 on 10.6.
Here is my attempt at Bash session history sharing. This will enable history sharing between
bash sessions in a way that the history counter does not get mixed up and history expansion
like
!number
will work (with some constraints).
Using Bash version 4.1.5 under Ubuntu 10.04 LTS (Lucid Lynx).
HISTSIZE=9000
HISTFILESIZE=$HISTSIZE
HISTCONTROL=ignorespace:ignoredups
_bash_history_sync() {
builtin history -a #1
HISTFILESIZE=$HISTSIZE #2
builtin history -c #3
builtin history -r #4
}
history() { #5
_bash_history_sync
builtin history "$@"
}
PROMPT_COMMAND=_bash_history_sync
Explanation:
Append the just entered line to the
$HISTFILE
(default is
.bash_history
). This will cause
$HISTFILE
to grow by one
line.
Setting the special variable
$HISTFILESIZE
to some value will cause Bash
to truncate
$HISTFILE
to be no longer than
$HISTFILESIZE
lines by
removing the oldest entries.
Clear the history of the running session. This will reduce the history counter by the
amount of
$HISTSIZE
.
Read the contents of
$HISTFILE
and insert them in to the current running
session history. this will raise the history counter by the amount of lines in
$HISTFILE
. Note that the line count of
$HISTFILE
is not
necessarily
$HISTFILESIZE
.
The
history()
function overrides the builtin history to make sure that the
history is synchronised before it is displayed. This is necessary for the history expansion
by number (more about this later).
More explanation:
Step 1 ensures that the command from the current running session gets written to the
global history file.
Step 4 ensures that the commands from the other sessions gets read in to the current
session history.
Because step 4 will raise the history counter, we need to reduce the counter in some
way. This is done in step 3.
In step 3 the history counter is reduced by
$HISTSIZE
. In step 4 the
history counter is raised by the number of lines in
$HISTFILE
. In step 2 we
make sure that the line count of
$HISTFILE
is exactly
$HISTSIZE
(this means that
$HISTFILESIZE
must be the same as
$HISTSIZE
).
About the constraints of the history expansion:
When using history expansion by number, you should always look up the number immediately
before using it. That means no bash prompt display between looking up the number and using
it. That usually means no enter and no ctrl+c.
Generally, once you have more than one Bash session, there is no guarantee whatsoever that
a history expansion by number will retain its value between two Bash prompt displays. Because
when
PROMPT_COMMAND
is executed the history from all other Bash sessions are
integrated in the history of the current session. If any other bash session has a new command
then the history numbers of the current session will be different.
I find this constraint reasonable. I have to look the number up every time anyway because
I can't remember arbitrary history numbers.
Usually I use the history expansion by number like this
$ history | grep something #note number
$ !number
I recommend using the following Bash options.
## reedit a history substitution line if it failed
shopt -s histreedit
## edit a recalled history line before executing
shopt -s histverify
Strange bugs:
Running the history command piped to anything will result that command to be listed in the
history twice. For example:
$ history | head
$ history | tail
$ history | grep foo
$ history | true
$ history | false
All will be listed in the history twice. I have no idea why.
Ideas for
improvements:
Modify the function
_bash_history_sync()
so it does not execute every
time. For example it should not execute after a
CTRL+C
on the prompt. I often
use
CTRL+C
to discard a long command line when I decide that I do not want to
execute that line. Sometimes I have to use
CTRL+C
to stop a Bash completion
script.
Commands from the current session should always be the most recent in the history of
the current session. This will also have the side effect that a given history number keeps
its value for history entries from this session.
I'm not aware of any way using
bash
. But it's one of the most popular features
of
zsh
.
Personally I prefer
zsh
over
bash
so I recommend trying it.
Here's the part of my
.zshrc
that deals with history:
SAVEHIST=10000 # Number of entries
HISTSIZE=10000
HISTFILE=~/.zsh/history # File
setopt APPEND_HISTORY # Don't erase history
setopt EXTENDED_HISTORY # Add additional data to history like timestamp
setopt INC_APPEND_HISTORY # Add immediately
setopt HIST_FIND_NO_DUPS # Don't show duplicates in search
setopt HIST_IGNORE_SPACE # Don't preserve spaces. You may want to turn it off
setopt NO_HIST_BEEP # Don't beep
setopt SHARE_HISTORY # Share history between session/terminals
If the histappend shell option is enabled (see the description of shopt under SHELL
BUILTIN COMMANDS below), the lines are appended to the history file, otherwise the history
file is over-written.
You can edit your BASH prompt to run the "history -a" and "history -r" that Muerr suggested:
savePS1=$PS1
(in case you mess something up, which is almost guaranteed)
PS1=$savePS1`history -a;history -r`
(note that these are back-ticks; they'll run history -a and history -r on every prompt.
Since they don't output any text, your prompt will be unchanged.
Once you've got your PS1 variable set up the way you want, set it permanently it in your
~/.bashrc file.
If you want to go back to your original prompt while testing, do:
PS1=$savePS1
I've done basic testing on this to ensure that it sort of works, but can't speak to any
side-effects from running
history -a;history -r
on every prompt.
The problem is the following: I have two shell windows A and B. In shell window A, I run
sleep 9999
, and (without waiting for the sleep to finish) in shell window B, I
want to be able to see
sleep 9999
in the bash history.
The reason why most other solutions here won't solve this problem is that they are writing
their history changes to the the history file using
PROMPT_COMMAND
or
PS1
, both of which are executing too late, only after the
sleep
9999
command has finished.
Here's an alternative that I use. It's cumbersome but it addresses the issue that @axel_c
mentioned where sometimes you may want to have a separate history instance in each terminal
(one for make, one for monitoring, one for vim, etc).
I keep a separate appended history file that I constantly update. I have the following
mapped to a hotkey:
history | grep -v history >> ~/master_history.txt
This appends all history from the current terminal to a file called master_history.txt in
your home dir.
I also have a separate hotkey to search through the master history file:
cat /home/toby/master_history.txt | grep -i
I use cat | grep because it leaves the cursor at the end to enter my regex. A less ugly
way to do this would be to add a couple of scripts to your path to accomplish these tasks,
but hotkeys work for my purposes. I also periodically will pull history down from other hosts
I've worked on and append that history to my master_history.txt file.
It's always nice to be able to quickly search and find that tricky regex you used or that
weird perl one-liner you came up with 7 months ago.
Right, So finally this annoyed me to find a decent solution:
# Write history after each command
_bash_history_append() {
builtin history -a
}
PROMPT_COMMAND="_bash_history_append; $PROMPT_COMMAND"
What this does is sort of amalgamation of what was said in this thread, except that I
don't understand why would you reload the global history after every command. I very rarely
care about what happens in other terminals, but I always run series of commands, say in one
terminal:
make
ls -lh target/*.foo
scp target/artifact.foo vm:~/
Here is my enhancement to @lesmana's
answer
. The main difference is that
concurrent windows don't share history. This means you can keep working in your windows,
without having context from other windows getting loaded into your current windows.
If you explicitly type 'history', OR if you open a new window then you get the history
from all previous windows.
Also, I use
this strategy
to
archive every command ever typed on my machine.
# Consistent and forever bash history
HISTSIZE=100000
HISTFILESIZE=$HISTSIZE
HISTCONTROL=ignorespace:ignoredups
_bash_history_sync() {
builtin history -a #1
HISTFILESIZE=$HISTSIZE #2
}
_bash_history_sync_and_reload() {
builtin history -a #1
HISTFILESIZE=$HISTSIZE #2
builtin history -c #3
builtin history -r #4
}
history() { #5
_bash_history_sync_and_reload
builtin history "$@"
}
export HISTTIMEFORMAT="%y/%m/%d %H:%M:%S "
PROMPT_COMMAND='history 1 >> ${HOME}/.bash_eternal_history'
PROMPT_COMMAND=_bash_history_sync;$PROMPT_COMMAND
I have written a script for setting a history file per session or task its based off the
following.
# write existing history to the old file
history -a
# set new historyfile
export HISTFILE="$1"
export HISET=$1
# touch the new file to make sure it exists
touch $HISTFILE
# load new history file
history -r $HISTFILE
It doesn't necessary save every history command but it saves the ones that i care about
and its easier to retrieve them then going through every command. My version also lists all
history files and provides the ability to search through them all.
I chose to put history in a file-per-tty, as multiple people can be working on the same
server - separating each session's commands makes it easier to audit.
# Convert /dev/nnn/X or /dev/nnnX to "nnnX"
HISTSUFFIX=`tty | sed 's/\///g;s/^dev//g'`
# History file is now .bash_history_pts0
HISTFILE=".bash_history_$HISTSUFFIX"
HISTTIMEFORMAT="%y-%m-%d %H:%M:%S "
HISTCONTROL=ignoredups:ignorespace
shopt -s histappend
HISTSIZE=1000
HISTFILESIZE=5000
History now looks like:
user@host:~# test 123
user@host:~# test 5451
user@host:~# history
1 15-08-11 10:09:58 test 123
2 15-08-11 10:10:00 test 5451
3 15-08-11 10:10:02 history
With the files looking like:
user@host:~# ls -la .bash*
-rw------- 1 root root 4275 Aug 11 09:42 .bash_history_pts0
-rw------- 1 root root 75 Aug 11 09:49 .bash_history_pts1
-rw-r--r-- 1 root root 3120 Aug 11 10:09 .bashrc
export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
and
PROMPT_COMMAND="$PROMPT_COMMAND;history -a; history -n"
If you run source ~/.bashrc, the $PROMPT_COMMAND will be like
"history -a; history -c; history -r history -a; history -c; history -r"
and
"history -a; history -n history -a; history -n"
This repetition occurs each time you run 'source ~/.bashrc'. You can check PROMPT_COMMAND
after each time you run 'source ~/.bashrc' by running 'echo $PROMPT_COMMAND'.
You could see some commands are apparently broken: "history -n history -a". But the good
news is that it still works, because other parts still form a valid command sequence (Just
involving some extra cost due to executing some commands repetitively. And not so clean.)
Personally I use the following simple version:
shopt -s histappend
PROMPT_COMMAND="history -a; history -c; history -r"
which has most of the functionalities while no such issue as mentioned above.
Another point to make is: there is really nothing magic . PROMPT_COMMAND is just a plain
bash environment variable. The commands in it get executed before you get bash prompt (the $
sign). For example, your PROMPT_COMMAND is "echo 123", and you run "ls" in your terminal. The
effect is like running "ls; echo 123".
$ PROMPT_COMMAND="echo 123"
output (Just like running 'PROMPT_COMMAND="echo 123"; $PROMPT_COMMAND'):
123
Run the following:
$ echo 3
output:
3
123
"history -a" is used to write the history commands in memory to ~/.bash_history
"history -c" is used to clear the history commands in memory
"history -r" is used to read history commands from ~/.bash_history to memory
Here is the snippet from my .bashrc and short explanations wherever needed:
# The following line ensures that history logs screen commands as well
shopt -s histappend
# This line makes the history file to be rewritten and reread at each bash prompt
PROMPT_COMMAND="$PROMPT_COMMAND;history -a; history -n"
# Have lots of history
HISTSIZE=100000 # remember the last 100000 commands
HISTFILESIZE=100000 # start truncating commands after 100000 lines
HISTCONTROL=ignoreboth # ignoreboth is shorthand for ignorespace and ignoredups
The HISTFILESIZE and HISTSIZE are personal preferences and you can change them as per your
tastes.
##############################################################################
# History Configuration for ZSH
##############################################################################
HISTSIZE=10000 #How many lines of history to keep in memory
HISTFILE=~/.zsh_history #Where to save history to disk
SAVEHIST=10000 #Number of history entries to save to disk
#HISTDUP=erase #Erase duplicates in the history file
setopt appendhistory #Append history to the history file (no overwriting)
setopt sharehistory #Share history across terminals
setopt incappendhistory #Immediately append to the history file, not just when a term is killed
"... ', the pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with '@' or ' ..."
Following some issues with scp (it did not like the presence of the bash bind command in my
.bashrc
file, apparently), I followed the advice of a clever guy on the Internet
(I just cannot find that post right now) that put at the top of its
.bashrc
file
this:
[[ ${-#*} != ${-} ]] || return
in order to make sure that the bash initialization is NOT executed unless in interactive
session.
Now, that works. However, I am not able to figure how it works. Could you enlighten
me?
According to
this
answer
, the
$-
is the current options set for the shell and I know that the
${}
is the so-called "substring" syntax for expanding variables.
However, I do not understand the
${-#*i}
part. And why
$-#*i
is
not the same as
${-#*i}
.
The word is expanded to produce a pattern just as in filename expansion. If the pattern
matches the beginning of the expanded value of parameter, then the result of the expansion
is the expanded value of parameter with the shortest matching pattern (the '#' case) or the
longest matching pattern (the '##' case) deleted.
If parameter is '@' or '
', the
pattern removal operation is applied to each positional parameter in turn, and the
expansion is the resultant list. If parameter is an array variable subscripted with '@' or
'
', the pattern removal operation is applied to each member of the array in turn, and
the expansion is the resultant list.
So basically what happens in
${-#*i}
is that
*i
is expanded, and
if it matches the beginning of the value of
$-
, then the result of the whole
expansion is
$-
with the shortest matching pattern between
*i
and
$-
deleted.
Example
VAR "baioasd"
echo ${VAR#*i};
outputs
oasd
.
In your case
If shell is interactive,
$-
will contain the letter 'i', so when you strip
the variable
$-
of the pattern
*i
you will get a string that is
different from the original
$-
(
[[ ${-#*i} != ${-} ]]
yelds true).
If shell is not interactive,
$-
does not contain the letter 'i' so the pattern
*i
does not match anything in
$-
and
[[ ${-#*i} != $-
]]
yelds false, and the
return
statement is executed.
To determine within a startup script whether or not Bash is running interactively, test
the value of the '-' special parameter. It contains i when the shell is interactive
Your substitution removes the string up to, and including the
i
and tests if
the substituted version is equal to the original string. They will be different if there is
i
in the
${-}
.
The reason you separate the login and non-login shell is because the .bashrc
file is reloaded every time you start a new copy of Bash.
The .profile file is loaded only when you either log in or use the appropriate
flag to tell Bash to act as a login shell.
Personally,
I put my PATH setup into a .profile file (because I sometimes
use other shells);
I put my Bash aliases and functions into my .bashrc file;
I put this
#!/bin/bash
# CRM .bash_profile Time-stamp: "2008-12-07 19:42"
# echo "Loading ${HOME}/.bash_profile"
source
~/.
profile
# get my PATH setup
source
~/.
bashrc
# get my Bash aliases
in my .bash_profile file.
Oh, and the reason you need to type bash again to get the new alias is that Bash
loads your .bashrc file when it starts but it doesn't reload it unless you tell it
to. You can reload the .bashrc file (and not need a second shell) by typing
source
~/.
bashrc
which loads the .bashrc file as if you had typed the commands directly to Bash.
You only log in once, and that's when ~/.bash_profile or ~/.profile
is read and executed. Since everything you run from your login shell inherits the login shell's
environment, you should put all your environment variables in there. Like LESS
, PATH , MANPATH , LC_* , ... For an example, see:
My .profile
Once you log in, you can run several more shells. Imagine logging in, running X, and in
X starting a few terminals with bash shells. That means your login shell started X, which inherited
your login shell's environment variables, which started your terminals, which started your
non-login bash shells. Your environment variables were passed along in the whole chain, so
your non-login shells don't need to load them anymore. Non-login shells only execute
~/.bashrc , not /.profile or ~/.bash_profile , for this exact
reason, so in there define everything that only applies to bash . That's functions, aliases,
bash-only variables like HISTSIZE (this is not an environment variable, don't export it!) ,
shell options with set and shopt , etc. For an example, see:
My .bashrc
Now, as part of UNIX peculiarity, a login-shell does NOT execute ~/.bashrc
but only ~/.profile or ~/.bash_profile , so you should source that
one manually from the latter. You'll see me do that in my ~/.profile too:
source ~/.bashrc .
When bash is invoked as an interactive login shell, or as a non-interactive shell with the
--login option, it first reads and executes commands from the file /etc/profile
, if that file exists. After reading that file, it looks for ~/.bash_profile ,
~/.bash_login , and ~/.profile , in that order, and reads and executes
commands from the first one that exists and is readable. The --noprofile option
may be used when the shell is started to inhibit this behavior.
When a login shell exits, bash reads and executes commands from the file ~/.bash_logout
, if it exists.
When an interactive shell that is not a login shell is started, bash reads and executes
commands from ~/.bashrc , if that file exists. This may be inhibited by using
the --norc option. The --rcfile file option will force bash to read
and execute commands from file instead of ~/.bashrc .
Thus, if you want to get the same behavior for both login shells and interactive non-login
shells, you should put all of your commands in either .bashrc or .bash_profile
, and then have the other file
source the first one.
I feel stupid: declare not found in bash scripting? I was anxious to get my feet wet, and I'm
only up to my toes before I'm stuck...this seems very very easy but I'm not sure what I've done
wrong. Below is the script and its output. What the heck am I missing?
______________________________________________________
#!/bin/bash
declare -a PROD[0]="computers" PROD[1]="HomeAutomation"
printf "${ PROD[*]}"
_______________________________________________________
products.sh: 6: declare: not found
products.sh: 8: Syntax error: Bad substitution
I ran what you posted (but at the command line, not in a script, though that should make no
significant difference), and got this:
Code:
-bash: ${ PROD[*]}: bad substitution
In other words, I couldn't reproduce your first problem, the "declare: not found" error. Try
the declare command by itself, on the command line.
And I got rid of the "bad substitution" problem when I removed the space which is between the
${ and the PROD on the printf line.
Hope this helps.
blackhole54
The previous poster identified your second problem.
As far as your first problem goes ... I am not a bash guru although I have written a number
of bash scripts. So far I have found no need for declare statements. I suspect that you might
not need it either. But if you do want to use it, the following does work:
Code:
#!/bin/bash
declare -a PROD
PROD[0]="computers"
PROD[1]="HomeAutomation"
printf "${PROD[*]}\n"
EDIT: My original post was based on an older version of bash. When I tried the declare statement
you posted I got an error message, but one that was different from yours. I just tried it on a
newer version of bash, and your declare statement worked fine. So it might depend on the version
of bash you are running. What I posted above runs fine on both versions.
Obviously cut out of a much more complex script that was more meaningful:
#!/bin/bash
function InitializeConfig(){
declare -r -g -A SHCFG_INIT=( [a]=b )
declare -r -g -A SHCFG_INIT=( [c]=d )
echo "This statement never gets executed"
}
set -o xtrace
InitializeConfig
echo "Back from function"
The output looks like this:
ronburk@ubuntu:~/ubucfg$ bash bug.sh
+ InitializeConfig
+ SHCFG_INIT=([a]=b)
+ declare -r -g -A SHCFG_INIT
+ SHCFG_INIT=([c]=d)
+ echo 'Back from function'
Back from function
Bash seems to silently execute a function return upon the second declare statement. Starting to think this really is a new bug, but happy to learn otherwise.
By gum, you're right! Then I get readonly warning on second declare, which is
reasonable, and the function completes. The xtrace output is also interesting;
implies
declare
without single quotes is really treated as two steps.
Ready to become superstitious about always single-quoting the argument to
declare
. Hard to see how popping the function stack can be anything
but a bug, though. –
Ron Burk
Jun 14 '15 at 23:58
I found
this thread in
bug-bash@gnu.org
related to
test -v
on an assoc array. In short, bash
implicitly did
test -v SHCFG_INIT[0]
in your script. I'm not sure this
behavior got introduced in 4.3.
You might want to use
declare -p
to workaround this...
if declare p SHCFG_INIT >/dev/null >& ; then
echo "looks like SHCFG_INIT not defined"
fi
====
Well, rats. I think your answer is correct, but also reveals I'm really asking
two separate questions when I thought they were probably the same issue. Since the
title better reflects what turns out to be the "other" question, I'll leave this up
for a while and see if anybody knows what's up with the mysterious implicit
function return... Thanks! –
Ron Burk
Jun 14 '15 at 17:01
Edited question to focus on the remaining issue. Thanks again for the answer on
the "-v" issue with associative arrays. –
Ron Burk
Jun 14 '15 at 17:55
Accepting this answer. Complete answer is here plus your comments above plus
(IMHO) there's a bug in this version of bash (can't see how there can be any excuse
for popping the function stack without warning). Thanks for your excellent research
on this! –
Ron Burk
Jun 21 '15 at 19:31
The
declare
or
typeset
builtins
, which are exact
synonyms, permit modifying the properties of variables. This is a very weak form of the
typing
[1]
available in certain
programming languages. The
declare
command is specific to version 2 or later of Bash.
The
typeset
command also works in ksh scripts.
declare/typeset options
-r
readonly
(
declare -r var1
works the same as
readonly var1
)
This is the rough equivalent of the
C
const
type qualifier. An attempt to
change the value of a
readonly
variable fails with an error message.
declare -i number
# The script will treat subsequent occurrences of "number" as an integer.
number=3
echo "Number = $number" # Number = 3
number=three
echo "Number = $number" # Number = 0
# Tries to evaluate the string "three" as an integer.
Certain arithmetic operations are permitted for declared integer variables without the
need for
expr
or
let
.
n=6/3
echo "n = $n" # n = 6/3
declare -i n
n=6/3
echo "n = $n" # n = 2
-a
array
declare -a indices
The variable
indices
will be treated as an
array
.
-f
function(s)
declare -f
A
declare -f
line with no arguments in a script causes a listing of all
the
functions
previously
defined in that script.
declare -f function_name
A
declare -f function_name
in a script lists just the function
named.
This declares a variable as available for exporting outside the environment of the
script itself.
-x var=$value
declare -x var3=373
The
declare
command permits assigning a value to a variable in the same statement
as setting its properties.
Example 9-10. Using
declare
to type variables
#!/bin/bash
func1 ()
{
echo This is a function.
}
declare -f # Lists the function above.
echo
declare -i var1 # var1 is an integer.
var1=2367
echo "var1 declared as $var1"
var1=var1+1 # Integer declaration eliminates the need for 'let'.
echo "var1 incremented by 1 is $var1."
# Attempt to change variable declared as integer.
echo "Attempting to change var1 to floating point value, 2367.1."
var1=2367.1 # Results in error message, with no change to variable.
echo "var1 is still $var1"
echo
declare -r var2=13.36 # 'declare' permits setting a variable property
#+ and simultaneously assigning it a value.
echo "var2 declared as $var2" # Attempt to change readonly variable.
var2=13.37 # Generates error message, and exit from script.
echo "var2 is still $var2" # This line will not execute.
exit 0 # Script will not exit here.
Using the
declare
builtin restricts the
scope
of a variable.
foo ()
{
FOO="bar"
}
bar ()
{
foo
echo $FOO
}
bar # Prints bar.
However . . .
foo (){
declare FOO="bar"
}
bar ()
{
foo
echo $FOO
}
bar # Prints nothing.
# Thank you, Michael Iatrou, for pointing this out.
9.2.1. Another use for
declare
The
declare
command can be helpful in identifying variables,
environmental
or otherwise. This
can be especially useful with
arrays
.
In this context,
typing
a variable means to classify it and restrict its
properties. For example, a variable
declared
or
typed
as an integer is no
longer available for
string operations
.
Script execution
Your perfect Bash script executes with syntax errors
If you
write Bash scripts with Bash specific syntax and features, run them with
Bash
, and
run them with Bash in
native mode
.
Wrong
no shebang
the interpreter used depends on the
OS
implementation and current shell
can be run by calling bash with the script name as an argument, e.g.
bash
myscript
#!/bin/sh
shebang
depends on what
/bin/sh
actually is, for a Bash it means compatiblity
mode, not native mode
Your script named "test" doesn't execute
Give it another name. The executable
test
already exists.
In Bash it's a builtin. With other shells, it might be an executable file. Either way, it's
bad name choice!
Workaround: You can call it using the pathname:
/home/user/bin/test
Globbing
Brace expansion is not globbing
The following command line is not
related to globbing (filename expansion):
# YOU EXPECT
# -i1.vob -i2.vob -i3.vob ....
echo -i{*.vob,}
# YOU GET
# -i*.vob -i
Why? The brace expansion is simple text substitution. All possible text formed by the prefix,
the postfix and the braces themselves are generated. In the example, these are only two:
-i*.vob
and
-i
. The filename expansion happens after that, so there is a
chance that
-i*.vob
is expanded to a filename - if you have files like
-ihello.vob
. But it definitely doesn't do what you expected.
Variables
Setting variables
The Dollar-Sign
There is no
$
(dollar-sign) when you reference the name of a variable! Bash is not PHP!
# THIS IS WRONG!
$myvar="Hello world!"
A variable name preceeded with a dollar-sign always means that the variable gets expanded .
In the example above, it might expand to nothing (because it wasn't set), effectively resulting
in
="Hello world!"
which definitely is wrong !
When you need the name of a variable, you write only the name , for example
(as shown above) to set variables:
picture=/usr/share/images/foo.png
to name variables to be used by the
read
builtin command:
read
picture
to name variables to be unset:
unset picture
When you need the content of a variable, you prefix its name with a dollar-sign , like
echo "The used picture is: $picture"
Whitespace
Putting spaces on either or both sides of the equal-sign (
=
) when assigning a value to a variable will fail.
# INCORRECT 1
example = Hello
# INCORRECT 2
example= Hello
# INCORRECT 3
example =Hello
The only valid form is no spaces between the variable name and assigned value
Expanding (using) variables
A typical beginner's trap is quoting.
As noted above, when you want to expand a variable i.e. "get the content", the variable name
needs to be prefixed with a dollar-sign. But, since Bash knows various ways to quote and does
word-splitting, the result isn't always the same.
Let's define an example variable containing text with spaces:
example="Hello world"
Used form
result
number of words
$example
Hello world
2
"$example"
Hello world
1
\$example
$example
1
'$example'
$example
1
If you use parameter expansion, you must use the name (
PATH
) of the
referenced variables/parameters. i.e. not (
$PATH
):
# WRONG!
echo "The first character of PATH is ${$PATH:0:1}"
# CORRECT
echo "The first character of PATH is ${PATH:0:1}"
Note that if you are using variables in
arithmetic expressions
, then the bare
name is allowed:
((a=$a+7)) # Add 7 to a
((a = a + 7)) # Add 7 to a. Identical to the previous command.
((a += 7)) # Add 7 to a. Identical to the previous command.
a=$((a+7)) # POSIX-compatible version of previous code.
Exporting
Exporting a variable means to give newly created (child-)processes a copy
of that variable. not copy a variable created in a child process to the parent process. The
following example does not work, since the variable
hello
is set in a child
process (the process you execute to start that script
./script.sh
):
Exporting is one-way. The direction is parent process to child process, not the reverse. The
above example will work, when you don't execute the script, but include ("source") it:
Exit codes
Reacting to exit codes
If you just want to react to an exit code,
regardless of its specific value, you don't need to use
$?
in a test command like
this:
grep
^root:
etc
passwd
>/
dev
null
>&
if
$?
-neq
then
echo
"root was not found - check the pub at the corner"
fi
This can be simplified to:
if
grep
^root:
etc
passwd
>/
dev
null
>&
then
echo
"root was not found - check the pub at the corner"
fi
Or, simpler yet:
grep
^root:
etc
passwd
>/
dev
null
>&
||
echo
"root was not found - check the pub at the corner"
If you need the specific value of
$?
, there's no other choice. But if you need
only a "true/false" exit indication, there's no need for
$?
.
Output vs. Return Value
It's important to remember the different ways to run a
child command, and whether you want the output, the return value, or neither.
When you want to run a command (or a pipeline) and save (or print) the output , whether as a
string or an array, you use Bash's
$(command)
syntax:
$(ls -l /tmp)
newvariable=$(printf "foo")
When you want to use the return value of a command, just use the command, or add ( ) to run
a command or pipeline in a subshell:
if grep someuser /etc/passwd ; then
# do something
fi
if ( w | grep someuser | grep sqlplus ) ; then
# someuser is logged in and running sqlplus
fi
Make sure you're using the form you intended:
# WRONG!
if $(grep ERROR /var/log/messages) ; then
# send alerts
fi
Purpose An array is a parameter that holds mappings from keys to values. Arrays are used
to store a collection of parameters into a parameter. Arrays (in any programming language) are a
useful and common composite data structure, and one of the most important scripting features in Bash
and other shells.
Here is an abstract representation of an array named NAMES . The indexes go from
0 to 3.
NAMES
0: Peter
1: Anna
2: Greg
3: Jan
Instead of using 4 separate variables, multiple related variables are grouped grouped together
into elements of the array, accessible by their key . If you want the second name,
ask for index 1 of the array NAMES . Indexing Bash supports two different types
of ksh-like one-dimensional arrays. Multidimensional arrays are not implemented .
Indexed arrays use positive integer numbers as keys. Indexed arrays are always sparse
, meaning indexes are not necessarily contiguous. All syntax used for both assigning and dereferencing
indexed arrays is an
arithmetic evaluation context (see
Referencing
). As in C and many other languages, the numerical array indexes start at 0 (zero). Indexed arrays
are the most common, useful, and portable type. Indexed arrays were first introduced to Bourne-like
shells by ksh88. Similar, partially compatible syntax was inherited by many derivatives including
Bash. Indexed arrays always carry the -a attribute.
Associative arrays (sometimes known as a "hash" or "dict") use arbitrary nonempty
strings as keys. In other words, associative arrays allow you to look up a value from a table
based upon its corresponding string label. Associative arrays are always unordered , they merely
associate key-value pairs. If you retrieve multiple values from the array at once, you
can't count on them coming out in the same order you put them in. Associative arrays always carry
the -A attribute, and unlike indexed arrays, Bash requires that they always be declared
explicitly (as indexed arrays are the default, see
declaration
). Associative arrays were first introduced in ksh93, and similar mechanisms were later adopted
by Zsh and Bash version 4. These three are currently the only POSIX-compatible shells with any
associative array support.
SyntaxReferencing To accommodate referring to array variables and their individual
elements, Bash extends the parameter naming scheme with a subscript suffix. Any valid ordinary scalar
parameter name is also a valid array name: [[:alpha:]_][[:alnum:]_]* . The parameter
name may be followed by an optional subscript enclosed in square brackets to refer to a member of
the array.
The overall syntax is arrname[subscript] - where for indexed arrays, subscript
is any valid arithmetic expression, and for associative arrays, any nonempty string. Subscripts are
first processed for parameter and arithmetic expansions, and command and process substitutions. When
used within parameter expansions or as an argument to the
unset builtin,
the special subscripts * and @ are also accepted which act upon arrays
analogously to the way the @ and * special parameters act upon the positional
parameters. In parsing the subscript, bash ignores any text that follows the closing bracket up to
the end of the parameter name.
With few exceptions, names of this form may be used anywhere ordinary parameter names are valid,
such as within arithmetic
expressions , parameter expansions
, and as arguments to builtins that accept parameter names. An array is a Bash parameter
that has been given the -a (for indexed) or -A (for associative) attributes
. However, any regular (non-special or positional) parameter may be validly referenced using a subscript,
because in most contexts, referring to the zeroth element of an array is synonymous with referring
to the array name without a subscript.
# "x" is an ordinary non-array parameter.
$ x=hi; printf '%s ' "$x" "${x[0]}"; echo "${_[0]}"
hi hi hi
The only exceptions to this rule are in a few cases where the array variable's name refers to
the array as a whole. This is the case for the unset builtin (see
destruction
) and when declaring an array without assigning any values (see
declaration
). Declaration The following explicitly give variables array attributes, making them arrays:
Syntax
Description
ARRAY=()
Declares an indexed array ARRAY and initializes it to be empty. This can also
be used to empty an existing array.
ARRAY[0]=
Generally sets the first element of an indexed array. If no array ARRAY existed
before, it is created.
declare -a ARRAY
Declares an indexed array ARRAY . An existing array is not initialized.
declare -A ARRAY
Declares an associative array ARRAY . This is the one and only way to create
associative arrays.
Storing values Storing values in arrays is quite as simple as storing values in normal variables.
Syntax
Description
ARRAY[N]=VALUE
Sets the element N of the indexed array ARRAY to VALUE
. N can be any valid
arithmetic expression
ARRAY[STRING]=VALUE
Sets the element indexed by STRING of the associative array ARRAY
.
ARRAY=VALUE
As above. If no index is given, as a default the zeroth element is set to VALUE
. Careful, this is even true of associative arrays - there is no error if no key is specified,
and the value is assigned to string index "0".
ARRAY=(E1 E2 )
Compound array assignment - sets the whole array ARRAY to the given list of
elements indexed sequentially starting at zero. The array is unset before assignment unless
the += operator is used. When the list is empty ( ARRAY=() ), the array will be
set to an empty array. This method obviously does not use explicit indexes. An associative
array can not be set like that! Clearing an associative array using ARRAY=() works.
ARRAY=([X]=E1 [Y]=E2 )
Compound assignment for indexed arrays with index-value pairs declared individually (here
for example X and Y ). X and Y are arithmetic expressions. This syntax
can be combined with the above - elements declared without an explicitly specified index are
assigned sequentially starting at either the last element with an explicit index, or zero.
ARRAY=([S1]=E1 [S2]=E2 )
Individual mass-setting for associative arrays . The named indexes (here: S1
and S2 ) are strings.
Expands to the value of the index N in the indexed array ARRAY
. If N is a negative number, it's treated as the offset from the maximum assigned
index (can't be used for assignment) - 1
${ARRAY[S]}
Expands to the value of the index S in the associative array ARRAY
.
Similar to
mass-expanding
positional parameters , this expands to all elements. If unquoted, both subscripts
* and @ expand to the same result, if quoted, @ expands
to all elements individually quoted, * expands to all elements quoted as a whole.
Similar to what this syntax does for the characters of a single string when doing
substring
expansion , this expands to M elements starting with element N
. This way you can mass-expand individual indexes. The rules for quoting and the subscripts
* and @ are the same as above for the other mass-expansions.
For clarification: When you use the subscripts @ or * for mass-expanding,
then the behaviour is exactly what it is for $@ and $* when
mass-expanding
the positional parameters . You should read this article to understand what's going on. Metadata
Syntax
Description
${#ARRAY[N]}
Expands to the length of an individual array member at index N ( stringlength
${#ARRAY[STRING]}
Expands to the length of an individual associative array member at index STRING
( stringlength )
${#ARRAY[@]} ${#ARRAY[*]}
Expands to the number of elements in ARRAY
${!ARRAY[@]} ${!ARRAY[*]}
Expands to the indexes in ARRAY since BASH 3.0
Destruction The
unset builtin command
is used to destroy (unset) arrays or individual elements of arrays.
Example: You are in a directory with a file named x1 , and you want to destroy an
array element x[1] , with
unset x[1]
then pathname expansion will expand to the filename x1 and break your processing!
Even worse, if nullglob is set, your array/index will disappear.
To avoid this, always quote the array name and index:
unset -v 'x[1]'
This applies generally to all commands which take variable names as arguments. Single quotes preferred.
UsageNumerical Index Numerical indexed arrays are easy to understand and easy to
use. The Purpose
and Indexing chapters
above more or less explain all the needed background theory.
Now, some examples and comments for you.
Let's say we have an array sentence which is initialized as follows:
sentence=(Be liberal in what you accept, and conservative in what you send)
Since no special code is there to prevent word splitting (no quotes), every word there will be
assigned to an individual array element. When you count the words you see, you should get 12. Now
let's see if Bash has the same opinion:
$ echo ${#sentence[@]}
12
Yes, 12. Fine. You can take this number to walk through the array. Just subtract 1 from the number
of elements, and start your walk at 0 (zero)
((n_elements=${#sentence[@]}, max_index=n_elements - 1))
for ((i = 0; i <= max_index; i++)); do
echo "Element $i: '${sentence[i]}'"
done
You always have to remember that, it seems newbies have problems sometimes. Please understand
that numerical array indexing begins at 0 (zero)
The method above, walking through an array by just knowing its number of elements, only works
for arrays where all elements are set, of course. If one element in the middle is removed, then the
calculation is nonsense, because the number of elements doesn't correspond to the highest used index
anymore (we call them " sparse arrays "). Associative (Bash 4) Associative arrays
(or hash tables ) are not much more complicated than numerical indexed arrays. The numerical
index value (in Bash a number starting at zero) just is replaced with an arbitrary string:
# declare -A, introduced with Bash 4 to declare an associative array
declare -A sentence
sentence[Begin]='Be liberal in what'
sentence[Middle]='you accept, and conservative'
sentence[End]='in what you send'
sentence['Very end']=...
Beware: don't rely on the fact that the elements are ordered in memory like they were
declared, it could look like this:
# output from 'set' command
sentence=([End]="in what you send" [Middle]="you accept, and conservative " [Begin]="Be liberal in what " ["Very end"]="...")
This effectively means, you can get the data back with "${sentence[@]}" , of course
(just like with numerical indexing), but you can't rely on a specific order. If you want to store
ordered data, or re-order data, go with numerical indexes. For associative arrays, you usually query
known index values:
for element in Begin Middle End "Very end"; do
printf "%s" "${sentence[$element]}"
done
printf "\n"
A nice code example: Checking for duplicate files using an associative array indexed with the
SHA sum of the files:
# Thanks to Tramp in #bash for the idea and the code
unset flist; declare -A flist;
while read -r sum fname; do
if [[ ${flist[$sum]} ]]; then
printf 'rm -- "%s" # Same as >%s<\n' "$fname" "${flist[$sum]}"
else
flist[$sum]="$fname"
fi
done < <(find . -type f -exec sha256sum {} +) >rmdups
Integer arrays Any type attributes applied to an array apply to all elements of the array.
If the integer attribute is set for either indexed or associative arrays, then values are considered
as arithmetic for both compound and ordinary assignment, and the += operator is modified in the same
way as for ordinary integer variables.
a[0] is assigned to the result of 2+4 . a[1] gets the result
of 2+2 . The last index in the first assignment is the result of a[2] ,
which has already been assigned as 4 , and its value is also given a[2]
.
This shows that even though any existing arrays named a in the current scope have
already been unset by using = instead of += to the compound assignment,
arithmetic variables within keys can self-reference any elements already assigned within the same
compound-assignment. With integer arrays this also applies to expressions to the right of the
= . (See
evaluation
order , the right side of an arithmetic assignment is typically evaluated first in Bash.)
The second compound assignment argument to declare uses += , so it appends after
the last element of the existing array rather than deleting it and creating a new array, so
a[5] gets 42 .
Lastly, the element whose index is the value of a[4] ( 4 ), gets
3 added to its existing value, making a[4] == 7 . Note that
having the integer attribute set this time causes += to add, rather than append a string, as it would
for a non-integer array.
The single quotes force the assignments to be evaluated in the environment of declare
. This is important because attributes are only applied to the assignment after assignment arguments
are processed. Without them the += compound assignment would have been invalid, and
strings would have been inserted into the integer array without evaluating the arithmetic. A special-case
of this is shown in the next section.
eval , but there are differences.) 'Todo: ' Discuss this in detail.
Indirection Arrays can be expanded indirectly using the indirect parameter expansion syntax.
Parameters whose values are of the form: name[index] , name[@] , or
name[*] when expanded indirectly produce the expected results. This is mainly useful
for passing arrays (especially multiple arrays) by name to a function.
This example is an "isSubset"-like predicate which returns true if all key-value pairs of the
array given as the first argument to isSubset correspond to a key-value of the array given as the
second argument. It demonstrates both indirect array expansion and indirect key-passing without eval
using the aforementioned special compound assignment expansion.
isSubset() {
local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")'
set -- "${@/%/[key]}"
(( ${#xkeys[@]} <= ${#ykeys[@]} )) || return 1
local key
for key in "${xkeys[@]}"; do
[[ ${!2+_} && ${!1} == ${!2} ]] || return 1
done
}
main() {
# "a" is a subset of "b"
local -a 'a=({0..5})' 'b=({0..10})'
isSubset a b
echo $? # true
# "a" contains a key not in "b"
local -a 'a=([5]=5 {6..11})' 'b=({0..10})'
isSubset a b
echo $? # false
# "a" contains an element whose value != the corresponding member of "b"
local -a 'a=([5]=5 6 8 9 10)' 'b=({0..10})'
isSubset a b
echo $? # false
}
main
This script is one way of implementing a crude multidimensional associative array by storing array
definitions in an array and referencing them through indirection. The script takes two keys and dynamically
calls a function whose name is resolved from the array.
callFuncs() {
# Set up indirect references as positional parameters to minimize local name collisions.
set -- "${@:1:3}" ${2+'a["$1"]' "$1"'["$2"]'}
# The only way to test for set but null parameters is unfortunately to test each individually.
local x
for x; do
[[ $x ]] || return 0
done
local -A a=(
[foo]='([r]=f [s]=g [t]=h)'
[bar]='([u]=i [v]=j [w]=k)'
[baz]='([x]=l [y]=m [z]=n)'
) ${4+${a["$1"]+"${1}=${!3}"}} # For example, if "$1" is "bar" then define a new array: bar=([u]=i [v]=j [w]=k)
${4+${a["$1"]+"${!4-:}"}} # Now just lookup the new array. for inputs: "bar" "v", the function named "j" will be called, which prints "j" to stdout.
}
main() {
# Define functions named {f..n} which just print their own names.
local fun='() { echo "$FUNCNAME"; }' x
for x in {f..n}; do
eval "${x}${fun}"
done
callFuncs "$@"
}
main "$@"
Bugs and Portability Considerations
Arrays are not specified by POSIX. One-dimensional indexed arrays are supported using similar
syntax and semantics by most Korn-like shells.
Associative arrays are supported via typeset -A in Bash 4, Zsh, and Ksh93.
In Ksh93, arrays whose types are not given explicitly are not necessarily indexed. Arrays
defined using compound assignments which specify subscripts are associative by default. In Bash,
associative arrays can only be created by explicitly declaring them as associative, otherwise
they are always indexed. In addition, ksh93 has several other compound structures whose types
can be determined by the compound assignment syntax used to create them.
In Ksh93, using the = compound assignment operator unsets the array, including
any attributes that have been set on the array prior to assignment. In order to preserve attributes,
you must use the += operator. However, declaring an associative array, then attempting
an a=( ) style compound assignment without specifying indexes is an error. I can't
explain this inconsistency.
$ ksh -c 'function f { typeset -a a; a=([0]=foo [1]=bar); typeset -p a; }; f' # Attribute is lost, and since subscripts are given, we default to associative.
typeset -A a=([0]=foo [1]=bar)
$ ksh -c 'function f { typeset -a a; a+=([0]=foo [1]=bar); typeset -p a; }; f' # Now using += gives us the expected results.
typeset -a a=(foo bar)
$ ksh -c 'function f { typeset -A a; a=(foo bar); typeset -p a; }; f' # On top of that, the reverse does NOT unset the attribute. No idea why.
ksh: f: line 1: cannot append index array to associative array a
Only Bash and mksh support compound assignment with mixed explicit subscripts and automatically
incrementing subscripts. In ksh93, in order to specify individual subscripts within a compound
assignment, all subscripts must be given (or none). Zsh doesn't support specifying individual
subscripts at all.
Appending to a compound assignment is a fairly portable way to append elements after the last
index of an array. In Bash, this also sets append mode for all individual assignments within the
compound assignment, such that if a lower subscript is specified, subsequent elements will be
appended to previous values. In ksh93, it causes subscripts to be ignored, forcing appending everything
after the last element. (Appending has different meaning due to support for multi-dimensional
arrays and nested compound datastructures.)
$ ksh -c 'function f { typeset -a a; a+=(foo bar baz); a+=([3]=blah [0]=bork [1]=blarg [2]=zooj); typeset -p a; }; f' # ksh93 forces appending to the array, disregarding subscripts
typeset -a a=(foo bar baz '[3]=blah' '[0]=bork' '[1]=blarg' '[2]=zooj')
$ bash -c 'function f { typeset -a a; a+=(foo bar baz); a+=(blah [0]=bork blarg zooj); typeset -p a; }; f' # Bash applies += to every individual subscript.
declare -a a='([0]="foobork" [1]="barblarg" [2]="bazzooj" [3]="blah")'
$ mksh -c 'function f { typeset -a a; a+=(foo bar baz); a+=(blah [0]=bork blarg zooj); typeset -p a; }; f' # Mksh does like Bash, but clobbers previous values rather than appending.
set -A a
typeset a[0]=bork
typeset a[1]=blarg
typeset a[2]=zooj
typeset a[3]=blah
In Bash and Zsh, the alternate value assignment parameter expansion ( ${arr[idx]:=foo}
) evaluates the subscript twice, first to determine whether to expand the alternate, and second
to determine the index to assign the alternate to. See
evaluation
order .
$ : ${_[$(echo $RANDOM >&2)1]:=$(echo hi >&2)}
13574
hi
14485
In Zsh, arrays are indexed starting at 1 in its default mode. Emulation modes are required
in order to get any kind of portability.
Zsh and mksh do not support compound assignment arguments to typeset .
Ksh88 didn't support modern compound array assignment syntax. The original (and most portable)
way to assign multiple elements is to use the set -A name arg1 arg2 syntax. This
is supported by almost all shells that support ksh-like arrays except for Bash. Additionally,
these shells usually support an optional -s argument to set which performs
lexicographic sorting on either array elements or the positional parameters. Bash has no built-in
sorting ability other than the usual comparison operators.
$ ksh -c 'set -A arr -- foo bar bork baz; typeset -p arr' # Classic array assignment syntax
typeset -a arr=(foo bar bork baz)
$ ksh -c 'set -sA arr -- foo bar bork baz; typeset -p arr' # Native sorting!
typeset -a arr=(bar baz bork foo)
$ mksh -c 'set -sA arr -- foo "[3]=bar" "[2]=baz" "[7]=bork"; typeset -p arr' # Probably a bug. I think the maintainer is aware of it.
set -A arr
typeset arr[2]=baz
typeset arr[3]=bar
typeset arr[7]=bork
typeset arr[8]=foo
Evaluation order for assignments involving arrays varies significantly depending on context.
Notably, the order of evaluating the subscript or the value first can change in almost every shell
for both expansions and arithmetic variables. See
evaluation
order for details.
Bash 4.1.* and below cannot use negative subscripts to address array indexes relative to the
highest-numbered index. You must use the subscript expansion, i.e. "${arr[@]:(-n):1}"
, to expand the nth-last element (or the next-highest indexed after n if arr[n]
is unset). In Bash 4.2, you may expand (but not assign to) a negative index. In Bash 4.3, ksh93,
and zsh, you may both assign and expand negative offsets.
ksh93 also has an additional slice notation: "${arr[n..m]}" where n
and m are arithmetic expressions. These are needed for use with multi-dimensional
arrays.
Assigning or referencing negative indexes in mksh causes wrap-around. The max index appears
to be UINT_MAX , which would be addressed by arr[-1] .
So far, Bash's -v var test doesn't support individual array subscripts. You may
supply an array name to test whether an array is defined, but can't check an element. ksh93's
-v supports both. Other shells lack a -v test.
Bugs
Fixed in 4.3 Bash 4.2.* and earlier considers each chunk of a compound assignment, including
the subscript for globbing. The subscript part is considered quoted, but any unquoted glob characters
on the right-hand side of the [ ]= will be clumped with the subscript and counted
as a glob. Therefore, you must quote anything on the right of the = sign. This is
fixed in 4.3, so that each subscript assignment statement is expanded following the same rules
as an ordinary assignment. This also works correctly in ksh93.
Each word (the entire assignment) is subject to globbing and brace expansion. This appears to
trigger the same strange expansion mode as let , eval , other declaration
commands, and maybe more.
Fixed in 4.3 Indirection combined with another modifier expands arrays to a single word.
Evaluation order Here are some of the nasty details of array assignment evaluation order.
You can use this testcase code
to generate these results.
Each testcase prints evaluation order for indexed array assignment
contexts. Each context is tested for expansions (represented by digits) and
arithmetic (letters), ordered from left to right within the expression. The
output corresponds to the way evaluation is re-ordered for each shell:
a[ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} No attributes
a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia a
a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia b
a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia a b
(( a[ $1 a ] = b[ $2 b ] ${c[ $3 c ]} )) No attributes
(( a[ $1 a ] = ${b[ $2 b ]:=c[ $3 c ]} )) typeset -ia b
a+=( [ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} [ $4 d ]=$(( $5 e )) ) typeset -a a
a+=( [ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} [ $4 d ]=${5}e ) typeset -ia a
bash: 4.2.42(1)-release
2 b 3 c 2 b 1 a
2 b 3 2 b 1 a c
2 b 3 2 b c 1 a
2 b 3 2 b c 1 a c
1 2 3 c b a
1 2 b 3 2 b c c a
1 2 b 3 c 2 b 4 5 e a d
1 2 b 3 2 b 4 5 a c d e
ksh93: Version AJM 93v- 2013-02-22
1 2 b b a
1 2 b b a
1 2 b b a
1 2 b b a
1 2 3 c b a
1 2 b b a
1 2 b b a 4 5 e d
1 2 b b a 4 5 d e
mksh: @(#)MIRBSD KSH R44 2013/02/24
2 b 3 c 1 a
2 b 3 1 a c
2 b 3 c 1 a
2 b 3 c 1 a
1 2 3 c a b
1 2 b 3 c a
1 2 b 3 c 4 5 e a d
1 2 b 3 4 5 a c d e
zsh: 5.0.2
2 b 3 c 2 b 1 a
2 b 3 2 b 1 a c
2 b 1 a
2 b 1 a
1 2 3 c b a
1 2 b a
1 2 b 3 c 2 b 4 5 e
1 2 b 3 2 b 4 5
Intro
The day will come when you want to give arguments to your scripts. These
arguments are known as positional parameters . Some relevant special parameters are described
below:
Parameter(s)
Description
$0
the first positional parameter, equivalent to
argv[0]
in C, see
the
first argument
$FUNCNAME
the function name (
attention
: inside a function,
$0
is still
the
$0
of the shell, not the function name)
all positional parameters except
$0
, see
mass usage
$@
all positional parameters except
$0
, see
mass usage
$#
the number of arguments, not counting
$0
These positional parameters reflect exactly what was given to the script when it was
called.
Option-switch parsing (e.g.
-h
for displaying help) is not performed at this
point.
See also
the
dictionary entry for "parameter"
.
The first argument
The very first argument you
can access is referenced as
$0
. It is usually set to the script's name exactly as
called, and it's set on shell initialization:
Testscript
- it just echos
$0
:
#!/bin/bash
echo "$0"
You see,
$0
is always set to the name the script is called with (
$
is the prompt ):
> ./testscript
./testscript
> /usr/bin/testscript
/usr/bin/testscript
However, this isn't true for login shells:
> echo "$0"
-bash
In other terms,
$0
is not a positional parameter, it's a special parameter
independent from the positional parameter list. It can be set to anything. In the ideal case
it's the pathname of the script, but since this gets set on invocation, the invoking program
can easily influence it (the
login
program does that for login shells, by
prefixing a dash, for example).
Inside a function,
$0
still behaves as described above. To get the function
name, use
$FUNCNAME
.
Shifting
The builtin command
shift
is
used to change the positional parameter values:
$1
will be discarded
$2
will become
$1
$3
will become
$2
in general:
$N
will become
$N-1
The command can take a number as argument: Number of positions to shift. e.g.
shift
4
shifts
$5
to
$1
.
Using them
Enough theory, you want
to access your script-arguments. Well, here we go.
One by one
One way is to access
specific parameters:
While useful in another situation, this way is lacks flexibility. The maximum number of
arguments is a fixedvalue - which is a bad idea if you write a script that takes many filenames
as arguments.
⇒ forget that one
Loops
There are several ways to loop through the positional
parameters.
You can code a
C-style for-loop
using
$#
as
the end value. On every iteration, the
shift
-command is used to shift the
argument list:
numargs=$#
for ((i=1 ; i <= numargs ; i++))
do
echo "$1"
shift
done
Not very stylish, but usable. The
numargs
variable is used to store the initial
value of
$#
because the shift command will change it as the script runs.
Another way to iterate one argument at a time is the
for
loop without a given
wordlist. The loop uses the positional parameters as a wordlist:
for arg
do
echo "$arg"
done
Advantage:
The positional parameters will be preserved
The next method is similar to the first example (the
for
loop), but it doesn't
test for reaching
$#
. It shifts and checks if
$1
still expands to
something, using the
test command
:
while [ "$1" ]
do
echo "$1"
shift
done
Looks nice, but has the disadvantage of stopping when
$1
is empty
(null-string). Let's modify it to run as long as
$1
is defined (but may be null),
using
parameter expansion for an
alternate value
:
while [ "${1+defined}" ]; do
echo "$1"
shift
done
Getopts
There is a
small tutorial dedicated to
''getopts''
(
under construction
).
Mass usage
All Positional
Parameters
Sometimes it's necessary to just "relay" or "pass" given arguments to another
program. It's very inefficient to do that in one of these loops, as you will destroy integrity,
most likely (spaces!).
The shell developers created
$*
and
$@
for this purpose.
As overview:
Syntax
Effective result
$*
$1 $2 $3 ${N}
$@
$1 $2 $3 ${N}
"$*"
"$1c$2c$3c c${N}"
"$@"
"$1" "$2" "$3" "${N}"
Without being quoted (double quotes), both have the same effect: All positional parameters
from
$1
to the last one used are expanded without any special handling.
When the
$*
special parameter is double quoted, it expands to the equivalent
of:
"$1c$2c$3c$4c ..$N"
, where 'c' is the first character of
IFS
.
But when the
$@
special parameter is used inside double quotes, it expands to
the equivanent of
"$1" "$2" "$3" "$4" .. "$N"
which reflects all positional parameters as they were set initially and passed to the script
or function. If you want to re-use your positional parameters to call another program (for
example in a wrapper-script), then this is the choice for you, use double quoted
"$@"
.
Well, let's just say: You almost always want a quoted
"$@"
!
Range Of
Positional Parameters
Another way to mass expand the positional parameters is similar to
what is possible for a range of characters using
substring expansion
on normal
parameters and the mass expansion range of
arrays
.
${@:START:COUNT}
${*:START:COUNT}
"${@:START:COUNT}"
"${*:START:COUNT}"
The rules for using
@
or
*
and quoting are the same as above. This
will expand
COUNT
number of positional parameters beginning at
START
.
COUNT
can be omitted (
${@:START}
), in which case, all positional
parameters beginning at
START
are expanded.
If
START
is negative, the positional parameters are numbered in reverse
starting with the last one.
COUNT
may not be negative, i.e. the element count may not be decremented.
Example:
START at the last positional parameter:
echo "${@: -1}"
Attention
: As of Bash 4, a
START
of
0
includes the
special parameter
$0
, i.e. the shell name or whatever $0 is set to, when the
positional parameters are in use. A
START
of
1
begins at
$1
. In Bash 3 and older, both
0
and
1
began at
$1
.
Setting Positional Parameters
Setting positional parameters with
command line arguments, is not the only way to set them. The
builtin command, set
may be used to
"artificially" change the positional parameters from inside the script or function:
set "This is" my new "set of" positional parameters
# RESULTS IN
# $1: This is
# $2: my
# $3: new
# $4: set of
# $5: positional
# $6: parameters
It's wise to signal "end of options" when setting positional parameters this way. If not,
the dashes might be interpreted as an option switch by
set
itself:
# both ways work, but behave differently. See the article about the set command!
set -- ...
set - ...
Alternately this will also preserve any verbose (-v) or tracing (-x) flags, which may
otherwise be reset by
set
set -$- ...
Production examples
Using a while loop
To make your program accept options as
standard command syntax:
COMMAND [options] <params>
# Like 'cat -A file.txt'
See simple option parsing code below. It's not that flexible. It doesn't auto-interpret
combined options (-fu USER) but it works and is a good rudimentary way to parse your
arguments.
#!/bin/sh
# Keeping options in alphabetical order makes it easy to add more.
while :
do
case "$1" in
-f | --file)
file="$2" # You may want to check validity of $2
shift 2
;;
-h | --help)
display_help # Call your function
# no shifting needed here, we're done.
exit 0
;;
-u | --user)
username="$2" # You may want to check validity of $2
shift 2
;;
-v | --verbose)
# It's better to assign a string, than a number like "verbose=1"
# because if you're debugging the script with "bash -x" code like this:
#
# if [ "$verbose" ] ...
#
# You will see:
#
# if [ "verbose" ] ...
#
# Instead of cryptic
#
# if [ "1" ] ...
#
verbose="verbose"
shift
;;
--) # End of all options
shift
break;
-*)
echo "Error: Unknown option: $1" >&2
exit 1
;;
*) # No more options
break
;;
esac
done
# End of file
Filter unwanted options with a wrapper script
This simple wrapper enables filtering
unwanted options (here:
-a
and
–all
for
ls
) out
of the command line. It reads the positional parameters and builds a filtered array consisting
of them, then calls
ls
with the new option set. It also respects the
–
as "end of options" for
ls
and doesn't change anything after
it:
#!/bin/bash
# simple ls(1) wrapper that doesn't allow the -a option
options=() # the buffer array for the parameters
eoo=0 # end of options reached
while [[ $1 ]]
do
if ! ((eoo)); then
case "$1" in
-a)
shift
;;
--all)
shift
;;
-[^-]*a*|-a?*)
options+=("${1//a}")
shift
;;
--)
eoo=1
options+=("$1")
shift
;;
*)
options+=("$1")
shift
;;
esac
else
options+=("$1")
# Another (worse) way of doing the same thing:
# options=("${options[@]}" "$1")
shift
fi
done
/bin/ls "${options[@]}"
The shell-developers invented $* and $@ for this purpose.
Without being quoted (double-quoted), both have the same effect: All positional parameters
from $1 to the last used one >are expanded, separated by the first character of IFS
(represented by "c" here, but usually a space):
$1c$2c$3c$4c........$N
Without double quotes, $* and $@ are expanding the positional parameters separated by only
space, not by IFS.
Once upon a time I was playing with
Windows Power Shell (WPSH) and discovered a very useful function for changing to commonly
visited directories. The function, called "go", which was written by
Peter Provost
, grew on me as I used WPSH ! so much so that I
decided to implement it in bash after my WPSH experiments ended.
The problem is simple. Users of command line interfaces tend to visit the same directories
repeatedly over the course of their work, and having a way to get to these oft-visited places
without a lot of typing is nice.
The solution entails maintaining a map of key-value pairs, where each key is an alias to a
value, which is itself a commonly visited directory. The "go" function will, when given a
string input, look that string up in the map, and if the key is found, move to the directory
indicated by the value.
The map itself is just a specially formatted text file with one key-value entry per line,
while each entry is separated into key-value components by the first encountered colon, with
the left side being interpreted as the entry's key and the right side as its value.
Keys are typically short easily typed strings, while values can be arbitrary path names, and
even contain references to environment variables. The effect of this is that "go" can respond
dynamically to the environment.
Finally, the "go" function finds the map file by referring to an environment variable called
"GO_FILE", which should have as its value the full path to the map.
Before I ran into this idea I had maintained a number of shell aliases, (i.e. alias
dwork='cd $WORK_DIR'), to achieve a similar end, but every time I wanted to add a new location
I was forced to edit my .bashrc file. Then I would subsequently have to resource it or enter
the alias again on the command line. Since I typically keep multiple shells open this is just a
pain, and so I didn't add new aliases very often. With this method, a new entry in the "go
file" is immediately available to all open shells without any extra finagling.
This functionality is related to CDPATH, but they are not replacements for one another.
Indeed CDPATH is the more appropriate solution when you want to be able to "cd" to all or most
of the sub-directories of some parent. On the other hand, "go" works very well for getting to a
single directory easily. For example you might not want "/usr/local" in your CDPATH and still
want an abbreviated way of getting to "/usr/local/share".
The code for the go function, as well as some brief documentation follows.
##############################################
# GO
#
# Inspired by some Windows Power Shell code
# from Peter Provost (peterprovost.org)
#
# Here are some examples entries:
# work:${WORK_DIR}
# source:${SOURCE_DIR}
# dev:/c/dev
# object:${USER_OBJECT_DIR}
# debug:${USER_OBJECT_DIR}/debug
###############################################
export GO_FILE=~/.go_locations
function go
{
if [ -z "$GO_FILE" ]
then
echo "The variable GO_FILE is not set."
return
fi
if [ ! -e "$GO_FILE" ]
then
echo "The 'go file': '$GO_FILE' does not exist."
return
fi
dest=""
oldIFS=${IFS}
IFS=$'\n'
for entry in `cat ${GO_FILE}`
do
if [ "$1" = ${entry%%:*} ]
then
#echo $entry
dest=${entry##*:}
break
fi
done
if [ -n "$dest" ]
then
# Expand variables in the go file.
#echo $dest
cd `eval echo $dest`
else
echo "Invalid location, valid locations are:"
cat $GO_FILE
fi
export IFS=${oldIFS}
}
Using
declare
(which will
detect
when it was called from within a
function and make the variable(s) local).
myfunc
()
local
var
=VALUE
# alternative, only when used INSIDE a function
declare
var
=VALUE
...
The
local
keyword (or declaring a variable using the
declare
command)
tags a variable to be treated
completely local and separate
inside the function where
it was declared:
foo
=external
printvalue
()
local
foo
=internal
echo
$foo
# this will print "external"
echo
$foo
# this will print "internal"
printvalue
# this will print - again - "external"
echo
$foo
The environment space is not directly related to the topic about scope, but it's worth
mentioning.
Every UNIX® process has a so-called
environment
. Other items, in addition to
variables, are saved there, the so-called
environment variables
. When a child process
is created (in Bash e.g. by simply executing another program, say
ls
to list
files), the whole environment
including the environment variables
is copied to the new
process. Reading that from the other side means: Only variables that are part of the
environment are available in the child process.
A variable can be tagged to be part of the environment using the
export
command:
# create a new variable and set it:
# -> This is a normal shell variable, not an environment variable!
myvariable
"Hello world."
# make the variable visible to all child processes:
# -> Make it an environment variable: "export" it
export
myvariable
Remember that the
exported
variable is a copy . There is no provision to "copy it
back to the parent." See the article about
Bash in the process tree
!
1)
under specific
circumstances, also by the shell itself
:
(colon) and input redirection. The
:
does nothing, it's a pseudo
command, so it does not care about standard input. In the following code example, you want to
test mail and logging, but not dump the database, or execute a shutdown:
#!/bin/bash
# Write info mails, do some tasks and bring down the system in a safe way
echo
"System halt requested"
mail
-s
"System halt"
netadmin
example.com
logger
-t
SYSHALT
"System halt requested"
##### The following "code block" is effectively ignored
:
<<
"SOMEWORD"
etc
init.d
mydatabase clean_stop
mydatabase_dump
var
db
db1
mnt
fsrv0
backups
db1
logger
-t
SYSHALT
"System halt: pre-shutdown actions done, now shutting down the system"
shutdown
-h
NOW
SOMEWORD
##### The ignored codeblock ends here
What happened? The
:
pseudo command was given some input by redirection (a
here-document) - the pseudo command didn't care about it, effectively, the entire block was
ignored.
The here-document-tag was quoted here to avoid substitutions in the "commented" text! Check
redirection with
here-documents
for more
Besides many bugfixes since Bash 3.2, Bash 4 will bring some interesting new features for
shell users and scripters. See also Bash changes for a small general
overview with more details.
Not all of the changes and news are included here, just the biggest or most interesting
ones. The changes to completion, and the readline component are not covered. Though, if you're
familiar with these parts of Bash (and Bash 4), feel free to write a chapter here.
The complete list of fixes and changes is in the CHANGES or NEWS file of your Bash 4
distribution.
The current available stable version is 4.2 release (February 13, 2011):
New or changed commands and keywordsThe new "coproc" keyword Bash 4
introduces the concepts of coprocesses, a well known feature of other shells. The basic concept
is simple: It will start any command in the background and set up an array that is populated
with accessible files that represent the filedescriptors of the started process.
In other words: It lets you start a process in background and communicate with its input and
output data streams.
See The coproc
keywordThe new "mapfile" builtin The mapfile builtin is able to map
the lines of a file directly into an array. This avoids having to fill an array yourself using
a loop. It enables you to define the range of lines to read, and optionally call a callback,
for example to display a progress bar.
See: The
mapfile builtin commandChanges to the "case" keyword The case
construct understands two new action list terminators:
The ;& terminator causes execution to continue with the next action list
(rather than terminate the case construct).
The ;;& terminator causes the case construct to test the next
given pattern instead of terminating the whole execution.
See The case
statementChanges to the "declare" builtin The -p option now prints all
attributes and values of declared variables (or functions, when used with -f ).
The output is fully re-usable as input.
The new option -l declares a variable in a way that the content is converted to
lowercase on assignment. For uppercase, the same applies to -u . The option
-c causes the content to be capitalized before assignment.
declare -A declares associative arrays (see below). Changes to the "read"
builtin The read builtin command has some interesting new features.
The -t option to specify a timeout value has been slightly tuned. It now
accepts fractional values and the special value 0 (zero). When -t 0 is specified,
read immediately returns with an exit status indicating if there's data waiting or
not. However, when a timeout is given, and the read builtin times out, any partial
data recieved up to the timeout is stored in the given variable, rather than lost. When a
timeout is hit, read exits with a code greater than 128.
A new option, -i , was introduced to be able to preload the input buffer with
some text (when Readline is used, with -e ). The user is able to change the text,
or press return to accept it.
See The read
builtin commandChanges to the "help" builtin The builtin itself didn't change much,
but the data displayed is more structured now. The help texts are in a better format, much
easier to read.
There are two new options: -d displays the summary of a help text,
-m displays a manpage-like format. Changes to the "ulimit" builtin Besides
the use of the 512 bytes blocksize everywhere in POSIX mode, ulimit supports two
new limits: -b for max socket buffer size and -T for max number of
threads. ExpansionsBrace Expansion The brace expansion was tuned to provide
expansion results with leading zeros when requesting a row of numbers.
See Brace
expansionParameter Expansion Methods to modify the case on expansion time have been
added.
On expansion time you can modify the syntax by adding operators to the parameter name.
See Case
modification on parameter expansionSubstring expansion When using substring
expansion on the positional parameters, a starting index of 0 now causes $0 to be prepended to
the list (if the positional parameters are used). Before, this expansion started with $1:
# this should display $0 on Bash v4, $1 on Bash v3
echo ${@:0:1}
Globbing There's a new shell option globstar . When
enabled, Bash will perform recursive globbing on ** – this means it matches
all directories and files from the current position in the filesystem, rather than only the
current level.
The new shell option dirspell enables
spelling corrections on directory names during globbing.
See Pathname
expansion (globbing)Associative Arrays Besides the classic method of integer
indexed arrays, Bash 4 supports associative arrays.
An associative array is an array indexed by an arbitrary string, something like
See ArraysRedirection There is a new &>> redirection operator, which
appends the standard output and standard error to the named file. This is the same as the good
old >>FILE 2>&1 notation.
The parser now understands |& as a synonym for 2>&1 | ,
which redirects the standard error for a command through a pipe.
If a command is not found, the shell attempts to execute a shell function named
command_not_found_handle , supplying the command words as the function
arguments. This can be used to display userfriendly messages or perform different command
searches
The behaviour of the set -e ( errexit ) mode was changed, it
now acts more intuitive (and is better documented in the manpage).
The output target for the xtrace ( set -x / set +x
) feature is configurable since Bash 4.1 (previously, it was fixed to stderr ):
a variable named BASH_XTRACEFD can be set to
the filedescriptor that should get the output
Bash 4.1 is able to log the history to syslog (only to be enabled at compile time in
config-top.h )
Update (Jan 26, 2016): I posted a
short update
about my
usage of persistent history.
For someone spending most of his time in front of a Linux terminal, history is very
important. But traditional bash history has a number of limitations, especially when multiple
terminals are involved (I sometimes have dozens open). Also it's not very good at preserving
just the history you're interested in across reboots.
There are many approaches to improve the situation; here I want to discuss one I've been
using very successfully in the past few months - a simple "persistent history" that keeps track
of history across terminal instances, saving it into a dot-file in my home directory (
~/.persistent_history
). All commands, from all terminal instances, are saved there,
forever. I found this tremendously useful in my work - it saves me time almost every day.
Why does it go into a
separate
history and not the
main
one which is
accessible by all the existing history manipulation tools? Because IMHO the latter is still
worthwhile to be kept separate for the simple need of bringing up recent commands in a single
terminal, without mixing up commands from other terminals. While the terminal is open, I want
the press "Up" and get the previous command, even if I've executed a 1000 other commands in
other terminal instances in the meantime.
Persistent history is very easy to set up. Here's the relevant portion of my
~/.bashrc
:
log_bash_persistent_history()
{
[[
$(history 1) =~ ^\ *[0-9]+\ +([^\ ]+\ [^\ ]+)\ +(.*)$
]]
local date_part="${BASH_REMATCH[1]}"
local command_part="${BASH_REMATCH[2]}"
if [ "$command_part" != "$PERSISTENT_HISTORY_LAST" ]
then
echo $date_part "|" "$command_part" >> ~/.persistent_history
export PERSISTENT_HISTORY_LAST="$command_part"
fi
}
# Stuff to do on PROMPT_COMMAND
run_on_prompt_command()
{
log_bash_persistent_history
}
PROMPT_COMMAND="run_on_prompt_command"
The format of the history file created by this is:
2013-06-09 17:48:11 | cat ~/.persistent_history
2013-06-09 17:49:17 | vi /home/eliben/.bashrc
2013-06-09 17:49:23 | ls
Note that an environment variable is used to avoid useless duplication (i.e. if I run
ls
twenty times in a row, it will only be recorded once).
OK, so we have
~/.persistent_history
, how do we
use
it? First, I should
say that it's not used very often, which kind of connects to the point I made earlier about
separating it from the much higher-use regular command history. Sometimes I just look into the
file with
vi
or
tail
, but mostly this alias does the trick for me:
alias phgrep='cat ~/.persistent_history|grep --color'
The alias name mirrors another alias I've been using for ages:
alias hgrep='history|grep --color'
Another tool for managing persistent history is a trimmer. I said earlier this file keeps
the history "forever", which is a scary word - what if it grows too large? Well, first of all -
worry not. At work my history file grew to about 2 MB after 3 months of heavy usage, and 2 MB
is pretty small these days. Appending to the end of a file is very, very quick (I'm pretty sure
it's a constant-time operation) so the size doesn't matter much. But trimming is easy:
tail -20000 ~/.persistent_history | tee ~/.persistent_history
Trims to the last 20000 lines. This should be sufficient for at least a couple of months of
history, and your workflow should not really rely on more than that :-)
Finally, what's the use of having a tool like this without employing it to collect some
useless statistics. Here's a histogram of the 15 most common commands I've used on my home
machine's terminal over the past 3 months:
ls : 865
vi : 863
hg : 741
cd : 512
ll : 289
pss : 245
hst : 200
python : 168
make : 167
git : 148
time : 94
python3 : 88
./python : 88
hpu : 82
cat : 80
Some explanation:
hst
is an alias for
hg st
.
hpu
is an alias for
hg pull -u
.
pss
is my
awesome pss tool
, and is the reason why you don't see any
calls to
grep
and
find
in the list. The proportion of Mercurial vs. git
commands is likely to change in the very
The bash session that is saved is the one for the terminal that is closed the latest.
If you want to save the commands for every session, you could use the trick explained
here.
export PROMPT_COMMAND='history -a'
To quote the manpage: "If set, the value is executed as a command prior to issuing each primary
prompt."
So every time my command has finished, it appends the unwritten history item to
~/.bash
ATTENTION: If you use multiple shell sessions and do not use this trick, you need to write
the history manually to preserver it using the command history -a
Anyone who has started a terminal in Linux is familiar with the default Bash prompt:
[
user
@
$host
~
]
$
But did you know is that this is completely customizable and can contain some very useful information?
Here are a few hidden treasures you can use to customize your Bash prompt.
How is the Bash prompt set?
The Bash prompt is set by the environment variable PS1 (Prompt String 1), which is used for interactive
shell prompts. There is also a PS2 variable, which is used when more input is required to complete
a Bash command.
[ dneary @ dhcp- 41 - 137 ~ ] $ export PS1 = "[Linux Rulez]$ "
[ Linux Rulez ] export PS2 = "... "
[ Linux Rulez ] if true ; then
... echo "Success!"
... fi
Success ! Where is the value of PS1 set?
PS1 is a regular environment variable.
The system default value is set in /etc/bashrc . On my system, the default prompt is set with
this line:
In the PROMPTING section of man bash , you can find a description of all the special characters
in PS1 and PS2 . The following are the default options:
\u : Username
\h : Short hostname
\W : Basename of the current working directory ( ~ for home, the end of the current directory
elsewhere)
\s : Shell name ( bash or sh , depending on how the shell is called)
\v : The shell's version
What other special strings can I use in the prompts?
There are a number of special strings that can be useful.
\d : Expands to the date in the format "Tue Jun 27"
\D{fmt} : Allows custom date formats!see man strftime for the available options
\D{%c} : Gives the date and time in the current locale
\n : Include a new line (see multi-line prompts below)
\w : The full path of the current working directory
\H : The full hostname for the current machine
\! : History number!you can run any previous command with its history number by using the
shell history event designator ! followed by the number for the specific command you are interested
in. (Using Linux history is yet another tutorial...)
There are many other special characters!you can see the full list in the PROMPTING section of
the Bash man page .
Multi-line prompts
If you use longer prompts (say if you include \H or \w or a full date-time ), you may want to
break things over two lines. Here is an example of a multi-line prompt, with the date, time, and
current working directory on one line, and username @hostname on the second line:
PS1
=
"\D{%c} \w
\n
[\u@\H]$ "
Are there any other interesting things I can do?
One thing people occasionally do is create colorful prompts. While I find them annoying and distracting,
you may like them. For example, to change the date-time above to display in red text, the directory
in cyan, and your username on a yellow background, you could try this:
\e[.. is an escape character. What follows is a special escape sequence to change the color
(or other characteristic) in the terminal
31m is red text ( 41m would be a red background)
36m is cyan text
1;43m declares a yellow background ( 1;33m would be yellow text)
\[\e[0m\] at the end resets the colors to the terminal defaults
You can find more colors and tips in the
Bash prompt HOWTO
. You can even make text inverted or blinking! Why on earth anyone would want to do this, I don't
know. But you can!
When you're in a version-controlled directory, it includes the VCS information (e.g. the git branch
and status), which is really handy if you do development.
Victorhck on 07 Jul 2017
Permalink An easy
drag and drop interface to build your own .bashrc/PS1 configuration
Today,
I have stumbled upon a collection of useful BASH scripts for heavy commandline users. These
scripts, known as Bash-Snippets , might be quite helpful for those who live in Terminal all
day. Want to check the weather of a place where you live? This script will do that for you.
Wondering what is the Stock prices? You can run the script that displays the current details of
a stock. Feel bored? You can watch some youtube videos. All from commandline. You don't need to
install any heavy memory consumable GUI applications.
Bash-Snippets provides the following 12 useful tools:
currency – Currency converter.
stocks – Provides certain Stock details.
weather – Displays weather details of your place.
crypt – Encrypt and decrypt files.
movies – Search and display a movie details.
taste – Recommendation engine that provides three similar items like the supplied
item (The items can be books, music, artists, movies, and games etc).
short – URL Shortner
geo – Provides the details of wan, lan, router, dns, mac, and ip.
cheat –
Provides cheat-sheets for various Linux commands
.
ytview – Watch YouTube from Terminal.
cloudup – A tool to backup your GitHub repositories to bitbucket.
qrify – Turns the given string into a qr code.
Bash-Snippets – A Collection Of Useful BASH Scripts For Heavy Commandline
Users
Installation
You can install these scripts on any OS that supports BASH.
This will ask you which scripts to install. Just type Y and press ENTER key to install the
respective script. If you don't want to install a particular script, type N and hit ENTER.
[Jul 16, 2017] Classifier by classifying them into folders of Xls, Docs, .png, .jpeg, vidoe, music, pdfs, images, ISO, etc.
If i'm
not wrong, all our download folder is pretty Sloppy compare with others because most of the
downloaded files are sitting over there and we can't delete blindly, which leads to lose some
important files. Also not possible to create bunch of folders based on the files and move
appropriate files into folder manually.
So, what to do to avoid this ? Better to organize files with help of classifier, later we
can delete unnecessary files easily. Classifier app was written in Python.
How to Organize directory ? Simple navigate to corresponding directory, where you want to
organize/classify your files and run the
classifier
command, it will take few mins
or more depends on the directory files count or quantity.
Make a note, there is no undo option, if you want to go back. So, finalize before run
classifier in directory. Also, it wont move folders.
Install Classifier in Linux through
pip
pip is a recommended tool for installing Python packages in Linux. Use pip command instead
of package manager to get latest build.
For Debian based systems.
$ sudo apt-get install python-pip
For RHEL/CentOS based systems.
$ sudo yum install python-pip
For Fedora
$ sudo dnf install python-pip
For openSUSE
$ sudo zypper install python-pip
For Arch Linux based systems
$ sudo pacman -S python-pip
Finally run the pip tool to install Classifier on Linux.
$ sudo pip install classifier
Organize pattern files into specific folders
First i will go with default option which will organize pattern files into specific folders.
This will create bunch of directories based on the file types and move them into specific
folders.
See my directory, how its looking now (Before run classifier command).
$ pwd
/home/magi/classifier
$ ls -lh
total 139M
-rw-r--r-- 1 magi magi 4.5M Mar 21 21:21 Aaluma_Doluma.mp3
-rw-r--r-- 1 magi magi 26K Mar 21 21:12 battery-monitor_0.4-xenial_all.deb
-rw-r--r-- 1 magi magi 24K Mar 21 21:12 buku-command-line-bookmark-manager-linux.png
-rw-r--r-- 1 magi magi 0 Mar 21 21:43 config.php
-rw-r--r-- 1 magi magi 25 Mar 21 21:13 core.py
-rw-r--r-- 1 magi magi 101K Mar 21 21:12 drawing.svg
-rw-r--r-- 1 magi magi 86M Mar 21 21:12 go1.8.linux-amd64.tar.gz
-rw-r--r-- 1 magi magi 28 Mar 21 21:13 index.html
-rw-r--r-- 1 magi magi 27 Mar 21 21:13 index.php
-rw-r--r-- 1 magi magi 48M Apr 30 2016 Kabali Tamil Movie _ Official Teaser _ Rajinikanth _ Radhika Apte _ Pa Ranjith-9mdJV5-eias.webm
-rw-r--r-- 1 magi magi 28 Mar 21 21:12 magi1.txt
-rw-r--r-- 1 magi magi 66 Mar 21 21:12 ppa.py
-rw-r--r-- 1 magi magi 1.1K Mar 21 21:12 Release.html
-rw-r--r-- 1 magi magi 45K Mar 21 21:12 v0.4.zip
Navigate to corresponding directory where you want to organize files, then run
classifier
command without any option to achieve it.
$ classifier
Scanning Files
Done!
See the Directory look, after run classifier command
$ ls -lh
total 44K
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Archives
-rw-r--r-- 1 magi magi 0 Mar 21 21:43 config.php
-rw-r--r-- 1 magi magi 25 Mar 21 21:13 core.py
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 DEBPackages
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Documents
-rw-r--r-- 1 magi magi 28 Mar 21 21:13 index.html
-rw-r--r-- 1 magi magi 27 Mar 21 21:13 index.php
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Music
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Pictures
-rw-r--r-- 1 magi magi 66 Mar 21 21:12 ppa.py
-rw-r--r-- 1 magi magi 1.1K Mar 21 21:12 Release.html
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Videos
Make a note, this will organize only general category files such docs, audio, video,
pictures, archive, etc and wont organize .py, .html, .php, etc.,.
Classify specific file
types into specific folder
To Classify specific file types into specific folder, just add
-st
(mention the
file type) &
-sf
(folder name) followed by classifier command.
For best understanding, i'm going to move
.py
,
.html
&
.php
files into
Development
folder. See the exact command to achieve
it.
If the folder doesn't exit, it will create the new one and organize the files into that. See
the following output. It created
Development
directory and moved all the files
inside the directory.
$ ls -lh
total 28K
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Archives
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 DEBPackages
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:51 Development
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Documents
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Music
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Pictures
drwxr-xr-x 2 magi magi 4.0K Mar 21 21:28 Videos
For better clarification, i have listed Development folder files.
$ ls -lh Development/
total 12K
-rw-r--r-- 1 magi magi 0 Mar 21 21:43 config.php
-rw-r--r-- 1 magi magi 25 Mar 21 21:13 core.py
-rw-r--r-- 1 magi magi 28 Mar 21 21:13 index.html
-rw-r--r-- 1 magi magi 27 Mar 21 21:13 index.php
-rw-r--r-- 1 magi magi 0 Mar 21 21:43 ppa.py
-rw-r--r-- 1 magi magi 0 Mar 21 21:43 Release.html
To Organize files by Date. It will organize current directory files based on the date.
Do you sometimes wonder how to use parameters with your scripts, and how to pass them to internal
functions or other scripts? Do you need to do simple validity tests on parameters or options, or
perform simple extraction and replacement operations on the parameter strings? This tip helps you
with parameter use and the various parameter expansions available in the bash shell.
About conditional, substring, and substitution parameter expansion
operators
Conditional parameter expansion
Conditional parameter expansion allows branching on whether the
parameter is unset, empty, or has content. Based on these conditions,
the parameter can be expanded to its value, a default value, or an
alternate value; throw a customizable error; or reassign the parameter
to a default value. The following table shows the conditional
parameter expansions-each row shows a parameter expansion using an
operator to potentially modify the expansion, with the columns showing
the result of that expansion given the parameter's status as indicated
in the column headers. Operators with the
':'
prefix
treat parameters with empty values as if they were unset.
parameter expansion
unset var
var=""
var="gnu"
${var-default}
default
-
gnu
${var:-default}
default
default
gnu
${var+alternate}
-
alternate
alternate
${var:+alternate}
-
-
alternate
${var?error}
error
-
gnu
${var:?error}
error
error
gnu
The
=
and
:=
operators in the table function
identically to
-
and
:-
, respectively, except that the
=
variants rebind the variable to the result of the expansion.
As an example, let's try opening a user's editor on a file
specified by the
OUT_FILE
variable. If either the
EDITOR
environment variable or our
OUT_FILE
variable is not specified,
we will have a problem. Using a conditional expansion, we can ensure
that when the
EDITOR
variable is expanded, we get the specified
value or at least a sane default:
Parameters can be expanded to just part of their contents, either
by offset or by removing content matching a pattern. When specifying a
substring offset, a length may optionally be specified. If running
Bash version 4.2 or greater, negative numbers may be used as offsets
from the end of the string. Note the parentheses used around the
negative offset, which ensure that Bash does not parse the expansion
as having the conditional default expansion operator from above:
$
location
=
"
CA 90095
"
$
echo
"
Zip Code:
${
location
:
3
}
"
Zip Code: 90095
$
echo
"
Zip Code:
${
location
:
(-5)
}
"
Zip Code: 90095
$
echo
"
State:
${
location
:
0
:
2
}
"
State: CA
Another way to take a substring is to remove characters from the
string matching a pattern, either from the left edge with the
#
and
##
operators or from the right edge with the
%
and
%%
operators. A useful mnemonic is that
#
appears left
of a comment and
%
appears right of a number. When the operator
is doubled, it matches greedily, as opposed to the single version,
which removes the most minimal set of characters matching the pattern.
var="open source"
parameter expansion
offset of 5
length of 4
${var:offset}
source
${var:offset:length}
sour
pattern of *o?
${var#pattern}
en source
${var##pattern}
rce
pattern of ?e*
${var%pattern}
open sour
${var%%pattern}
o
The pattern-matching used is the same as with filename globbing:
*
matches zero or more of any character,
?
matches exactly
one of any character,
[...]
brackets introduce a character
class match against a single character, supporting negation (
^
),
as well as the posix character classes, e.g. . By excising characters
from our string in this manner, we can take a substring without first
knowing the offset of the data we need:
The same types of patterns are used for substitution in parameter
expansion. Substitution is introduced with the
/
or
//
operators, followed by two arguments separated by another
/
representing the pattern and the string to substitute. The
pattern matching is always greedy, so the doubled version of the
operator, in this case, causes all matches of the pattern to be
replaced in the variable's expansion, while the singleton version
replaces only the leftmost.
var="free and open"
parameter expansion
pattern of
string of _
${var/pattern/string}
free_and open
${var//pattern/string}
free_and_open
The wealth of parameter expansion modifiers transforms Bash
variables and other parameters into powerful tools beyond simple value
stores. At the very least, it is important to understand how parameter
expansion works when reading Bash scripts, but I suspect that not
unlike myself, many of you will enjoy the conciseness and
expressiveness that these expansion modifiers bring to your scripts as
well as your interactive sessions.
ShellCheck
is a static
analysis tool that shows warnings and
suggestions concerning bad code in bash/sh
shell scripts. It can be used in several
ways: from the web by pasting your shell
script in an online editor (Ace – a
standalone code editor written in
JavaScript) in
https://www.shellcheck.net
(it is always
synchronized to the latest git commit, and
is the simplest way to give ShellCheck a go)
for instant feedback.
Alternatively, you
can install it on your machine and run it
from the terminal, integrate it with your
text editor as well as in your build or test
suites.
There are three things ShellCheck does
primarily:
It points out and explains typical
beginner's syntax issues that cause a
shell to give cryptic error messages.
It points out and explains typical
intermediate level semantic problems
that cause a shell to behave strangely
and counter-intuitively.
It also points out subtle caveats,
corner cases and pitfalls that may cause
an advanced user's otherwise working
script to fail under future
circumstances.
In this article, we will show how to
install and use ShellCheck in the various
ways to find bugs or bad code in your shell
scripts in Linux.
How to Install and Use ShellCheck in
Linux
ShellCheck
can be easily
installed locally through your package
manager as shown.
Once ShellCheck installed, let's take a
look at how to use ShellCheck in the various
methods we mentioned before.
Using ShellCheck From the Web
Go to
https://www.shellcheck.net
and paste
your script in the Ace editor provided, you
will view the output at the bottom of the
editor as shown in the screen shot below.
In the following example, the test shell
script consists of the following lines:
From the screenshot above, the first two
variables
E_NOTROOT
and
E_MINARGS
have been
declared but are unused, ShellCheck reports
these as "suggestive errors":
SC2034: E_NOTROOT appears unused. Verify it or export it.
SC2034: E_MINARGS appears unused. Verify it or export it.
Then secondly, the wrong name (in the
statement
echo $E_NONROOT
)
was used to
echo variable E_NOTROOT
,
that is why ShellCheck shows the error:
SC2153: Possible misspelling: E_NONROOT may not be assigned, but E_NOTROOT is
Again when you look at the
echo commands
, the variables have not
been double quoted (helps to prevent
globbing and word splitting), therefore
Shell Check shows the warning:
SC2086: Double quote to prevent globbing and word splitting.
Using ShellCheck From the Terminal
You can also run ShellCheck from the
command-line, we'll use the same shell
script above as follows:
$ shellcheck test.sh
ShellCheck – Checks Bad Code in Shell
Scripts
Using ShellCheck From the Text Editor
You can also view
ShellCheck
suggestions and warnings directly in a
variety of editors, this is probably a more
efficient way of using ShellCheck, once you
save a files, it shows you any errors in the
code.
In
Vim
, use ALE or
Syntastic (we will use this):
Start by installing
Pathogen
so that it's easy to install syntastic. Run
the commands below to get the
pathogen.vim
file and the
directories it needs:
Once you have installed pathogen, and you
now can put syntastic into
~/.vim/bundle
as follows:
# cd ~/.vim/bundle && git clone --depth=1 https://github.com/vim-syntastic/syntastic.git
Next, close vim and start it back up to
reload it, then type the command below:
:Helptags
If all goes well, you should have
ShellCheck
integrated with
Vim
, the following screenshots show
how it works using the same script above.
Check Bad Shell Script Code in Vim
In case you get an error after following
the steps above, then you possibly didn't
install
Pathogen
correctly.
Redo the steps but this ensure that you did
the following:
Created both the
~/.vim/autoload
and
~/.vim/bundle
directories.
Added the execute pathogen#infect()
line to your
~/.vimrc
file.
Did the git clone of syntastic
inside
~/.vim/bundle
.
Use appropriate permissions to
access all of the above directories.
You can also use other editors to check
bad code in shell scripts like:
That's it! In this article, we showed how
to install and use
ShellCheck
to finds bugs or bad code in your shell
scripts in Linux. Share your thoughts with
us via the comment section below.
Do you know of any other similar tools
out there? If yes, then share info about
them in the comments as well.
Share
+
0
9
16
If You Appreciate What We Do Here On TecMint,
You Should Consider:
Aaron Kili is a Linux and
F.O.S.S enthusiast, an upcoming Linux SysAdmin, web
developer, and currently a content creator for
TecMint who loves working with computers and
strongly believes in sharing knowledge.
Your name can also be listed here. Got a tip?
Submit it here
to become an
TecMint
author.
The typical UNIX® administrator has a key range of utilities, tricks,
and systems he or she uses regularly to aid in the process of
administration. There are key utilities, command-line chains, and scripts
that are used to simplify different processes. Some of these tools come
with the operating system, but a majority of the tricks come through
years of experience and a desire to ease the system administrator's life.
The focus of this series is on getting the most from the available tools
across a range of different UNIX environments, including methods of
simplifying administration in a heterogeneous environment.
The unattended script problem
There are many issues around executing unattended scripts-that is,
scripts that you run either automatically through a service like cron or
at
commands.
The default mode of cron and
at
commands, for example, is
for the output of the script to be captured and then emailed to the user
that ran the script. You don't always want the user to get the email that
cron sends by default (especially if everything ran fine)-sometimes the
user who ran the script and the person actually responsible for
monitoring that output are different.
Therefore, you need better methods for trapping and identifying errors
within the script, better methods for communicating problems, and
optional successes to the appropriate person.
Getting the scripts set up correctly is vital; you need to ensure that
the script is configured in such a way that it's easy to maintain and
that the script runs effectively. You also need to be able to trap errors
and output from programs and ensure the security and validity of the
environment in which the script executes. Read along to find out how to
do all of this.
Setting up the environment
Before getting into the uses of unattended scripts, you need to make
sure that you have set up your environment properly. There are various
elements that need to be explicitly configured as part of your script,
and taking the time to do this not only ensures that your script runs
properly, but it also makes the script easier to maintain.
Some things you might need to think about include:
Search path for applications
Search path for libraries
Directory locations
Creating directories or paths
Common files
Some of these elements are straightforward enough to organize. For
example, you can set the path using the following in most
Bourne-compatible shells (sh, Bash, ksh, and zsh):
1
PATH=/usr/bin:/bin:/usr/sbin
For directory and file locations, just set a variable at the header of
the script. You can then use the variable in each place where you would
have used the filename. For example, when writing to a log file, you
might use
Listing 1
.
By setting the name once and then using the variable, you ensure that
you don't get the filename wrong, and if you need to change the filename
name, you only need to change the name once.
Using a single filename and variable also makes it very easy to create
a complex filename. For example, adding a date to your log filename is
made easier by using the
date
command with a format
specification:
1
DATE='date +%Y%m%d.%H%M'
The above command creates a string containing the date in the format
YYYYMMDD.HHMM, for example, 20070524.2359. You can insert that date
variable into a filename so that your log file is tagged according to the
date it was created.
If you are not using a date/time unique identifier in the log
filename, it's a good idea to insert some other unique identifier in case
two scripts are run simultaneously. If your script is writing to the same
file from two different processes, you will end up either with corrupted
information or missing information.
All shells support a unique shell ID, based on the shell process ID,
and are accessible through the special
$$
variable name. By
using a global log variable, you can easily create a unique file to be
used for logging:
1
LOGFILE=/tmp/$$.err
You can also apply the same global variable principles to directories:
1
LOGDIR=/var/log/my_app
To ensure that the directories are created, use the
-p
option for mkdir to create the entire path of the directory you want to
use:
1
mkdir -p $LOGDIR
Fortunately, this format won't complain if the directories already
exist, which makes it ideal for running in an unattended script.
Finally, it is generally a good idea to use full path names rather
than localized paths in your unattended scripts so that you can use the
previous principles together.
Listing 2. Using full path names in
unattended scripts
Now that you've set up the environment, let's look at how you can use
these principles to help with the general, unattended scripts.
Writing a log file
Probably the simplest improvement you can make to your scripts is to
write the output from your script to a log file. You might not think this
is necessary, but the default operation of cron is to save the output
from the script or command that was executed, and then email it to the
user who owned the crontab or at job.
This is less than perfect for a number of reasons. First of all, the
configured user that might be running the script might not be the same as
the real person that needs to handle the output. You might be running the
script as root, even though the output of the script or command when run
needs to go to somebody else. Setting up a general filter or redirection
won't work if you want to send the output of different commands to
different users.
The second reason is a more fundamental one. Unless something goes
wrong, it's not necessary to receive the output from a script . The cron
daemon sends you the output from stdout and stderr, which means that you
get a copy of the output, even if the script executed successfully.
The final reason is about the management and organization of the
information and output generated. Email is not always an efficient way of
recording and tracking the output from the scripts that are run
automatically. Maybe you just want to keep an archive of the log file
that was a success or email a copy of the error log in the event of a
problem.
Writing out to a log file can be handled in a number of different
ways. The most straightforward way is to redirect output to a file for
each command (see
Listing 3
).
Listing 3. Redirecting output to a file
1
2
cd /shared
rsync --delete
--recursive . /backups/shared >$LOGFILE
If you want to combine error and standard output into a single file,
use numbered redirection (see
Listing 4
).
Listing 4. Combining error and standard
output into a single file
1
2
cd /shared
rsync --delete
--recursive . /backups/shared >$LOGFILE 2>&1
Listing 4
writes out the information to the same log file.
You might also want to write out the information to separate files
(see
Listing 5
).
Listing 5. Writing out information to
separate files
1
2
cd /shared
rsync --delete
--recursive . /backups/shared >$LOGFILE 2>$ERRFILE
For multiple commands, the redirections can get complex and
repetitive. You must ensure, for example, that you are appending, not
overwriting, information to the log file (see
Listing 6
).
Listing 6. Appending information to the log
file
1
2
cd /etc
rsync --delete
--recursive . /backups/etc >>$LOGFILE >>$ERRFILE
A simpler solution, if your shell supports it, is to use an inline
block for a group of commands, and then to redirect the output from the
block as a whole. The result is that you can rewrite the lines in
Listing 7
using the structure in
Listing 8
.
Listing 7. Logging in long form
1
2
3
4
5
cd /shared
rsync --delete
--recursive . /backups/shared >$LOGFILE 2>$ERRFILE
cd /etc
rsync --delete
--recursive . /backups/etc >>$LOGFILE 2>>$ERRFILE
Listing 8
shows an inline block for grouping commands.
Listing 8. Logging using a block
1
2
3
4
5
6
7
8
{
cd
/shared
rsync
--delete --recursive . /backups/shared
cd
/etc
rsync
--delete --recursive . /backups/etc
} >$LOGFILE
2>$ERRFILE
The enclosing braces imply a subshell so that all the commands in the
block are executed as if part of a separate process (although no
secondary shell is created, the enclosing block is just treated as a
different logical environment). Using the subshell, you can collectively
redirect their standard and error output for the entire block instead of
for each individual command.
Trapping errors and reporting them
One of the main advantages of the subshell is that you can place a
wrapper around the main content of the script, redirect the errors, and
then send a formatted email with the status of the script execution.
For example,
Listing 9
shows a more complete script that sets up the environment,
executes the actual commands and bulk of the process, traps the output,
and then sends an email with the output and error information.
Listing 9. Using a subshell for emailing a
more useful log
If you use the subshell trick and your shell supports shell options
(Bash, ksh, and zsh), then you might want to optionally set some shell
options to ensure that the block is terminated correctly on an error. For
example, the
-e
(errexit) option within Bash ensures that
the shell terminates when a simple command (for example, any external
command called through the script) causes immediate termination of the
shell.
In
Listing 9
, for example, if the first rsync failed, then the subshell
would just continue and run the next command. However, there are times
when you want to stop the moment a command fails because continuing could
be more damaging. By setting errexit, the subshell immediately terminates
when the first command stops.
Setting options and ensuring security
Another issue with automated scripts is ensuring the security of the
script and, in particular, ensuring that script does not fail because of
bad configuration. You can use shell options for this process.
Other options you might want to set in a shell-independent manner (and
the richer the shell, the better, as a rule, at trapping these
instances). In the Bash shell, for example,
-u
ensures that
any unset variables are treated as an error. This can be useful to ensure
that an unattended script does not try to execute when a required
variable has not been configured correctly.
The
-C
option (noclobber) ensures that files are not
overwritten if they already exist, and it can prevent the script from
overwriting files it shouldn't have access too (for example, the system
files), unless the script has the correct commands to delete the original
file first.
Each of these options can be set using the
set
command
(see
Listing 10
).
Listing 10. Using the set command to set
options
1
2
set -e
set -C
You can use a plus sign before the option to disable it.
Another area where you might want to improve the security and
environment of your script is to use resource limits. Resource limits can
be set by the
ulimit
command, which is generally specific to
the shell, and enable you to limit the size of files, cores, memory use,
and even the duration of the script to ensure that the script does not
run away with itself.
For example, you can set CPU time in seconds using the following
command:
1
ulimit -t 600
Although ulimit does not offer complete protection, it helps in those
scripts where the potential for the script to run away with itself, or a
program to suddenly use a large amount of memory, might become a problem.
Capturing faults
You have already seen how to trap errors, output, and create logs that
can be emailed to the appropriate person when they occur, but what if you
want to be more specific about the errors and responses?
Two tools are useful here. The first is the return status from a
command, and the second is the
trap
command within your
shell.
The return status from a command can be used to identify whether a
particular command ran correctly, or whether it generated some sort of
error. The exact meaning for a specific return status code is unique to a
particular command (check the man pages), but a generally accepted
principle is that an error code of zero means that the command executed
correctly.
For example, imagine that you want to trap an error when trying to
create a directory. You can check the
$?
variable after mkdir
and then email the output, as shown in
Listing 11
.
Listing 11. Trapping return status
1
2
3
4
5
6
7
8
ERRLOG=/tmp/$$.err
mkdir /tmp 2>>$ERRLOG
if [ $? -ne 0 ]
then
mailx
-s "Script failed when making directory" admin
<$ERRLOG
exit
1
fi
Incidentally, you can use the return status code information inline by
chaining commands with the && or || symbols to act as an
and
,
or
, or
type
statement. For example, say you
want to ensure that the directory gets created and the command gets
executed but, if the directory is not created, the command does not get
executed. You could do that using an
if
statement (see
Listing 12
).
Listing 12. Ensuring that a directory is
created before executing a command
1
2
3
4
5
mkdir /tmp/out
if [ $? -eq 0 ]
then
do_something
fi
The above statement basically reads, "Make a directory and, if it
completes successfully, also run the command." In essence, only do the
second command if the first completes correctly.
The || symbol works in the opposite way; if the first command does not
complete successfully, then execute the second. This can be useful for
trapping situations where a command would raise an error, but instead
provides an alternative solution. For example, when changing to a
directory, you might use the line:
1
cd /tmp/out || mkdir
/tmp/out
This line of code tries to change the directory and, if it fails,
(probably because the directory does not exist), you make it.
Furthermore, you can combine these statements together. In the previous
example, of course, what you want to do is change to the directory, or
create it and then change to that directory if it doesn't already exist.
You can write that in one line as:
1
cd /tmp/out || mkdir
/tmp/out && cd /tmp/out
The
trap
command is a more generalized solution for
trapping more serious errors based on the signals raised when a command
fails, such as core dump, memory error, or when a command has been
forcibly terminated by a
kill
command.
To use trap, you specify the command or function to be executed when
the signal is trapped, and the signal number or numbers that you want to
trap, as shown here in
Listing 13
.
You can trap any signal in this way and it can be a good way of
ensuring that a program that crashes out is caught and trapped
effectively and reported.
Identifying reportable errors
Throughout this article, you've looked at ways of trapping errors,
saving the output, and recording issues so that they can be dealt with
and reported. However, what if the script or commands that you are using
naturally output error information that you want to be able to use and
report on but that you don't always want to know about?
There is no easy solution to this problem, but you can use a
combination of the techniques shown in this article to log errors and
information, read or filter the information, and mail and report or
display it accordingly.
A simple way to do this is to choose which parts of the command that
you output and report to the logs. Alternatively, you can post-process
the logs to select or filter out the output that you need.
For example, say you have a script that builds a document in the
background using the Formatting Objects Processor (FOP) system from
Apache to generate a PDF version of the document. Unfortunately in the
process, a number of errors are generated about hyphenation. These are
errors that you know about, but they don't affect the output quality. In
the script that generates the file, just filter out these lines from the
error log:
1
sed -e
'/hyphenation/d' <error.log
>mailerror.log
If there were no other errors, the mailerror.log file will be empty,
and email is sent with the error information.
Summary
In this article, you've looked at how to run commands in an unattended
script, captured their output, and monitored the execution of different
commands in the script. You can log the information in many ways, for
example, on a command-by-command or global basis, and check and report on
the progress.
For error trapping, you can monitor output and result codes, and you
can even set up global traps that identify problems and trap them during
execution for reporting purposes. The result is a range of options that
handle and report problems for scripts that are running on their own and
where their ability to recover from errors and problems is critical.
So far we have seen two types of variables:
character strings and integers. The third type of variable the Korn shell supports is an
array
. As you may know, an array is like a list of things; you
can refer to specific elements in an array with integer
indices
,
so that
a[i]
refers to the
i
th element
of array
a
.
The Korn shell provides an array facility that, while useful, is much more
limited than analogous features in conventional programming languages. In particular,
arrays can be only one-dimensional (i.e., no arrays of arrays), and they are limited to
1024 elements. Indices can start at 0.
There are two ways to assign
values to elements of an array. The first is the most intuitive: you can use the standard
shell variable assignment syntax with the array index in brackets (
[]
). For example:
nicknames[2]=bob
nicknames[3]=ed
puts the values
bob
and
ed
into the elements of the array
nicknames
with indices 2 and 3, respectively. As with regular shell variables, values
assigned to array elements are treated as character strings unless the assignment is
preceded by
let
.
creates the array
aname
(if it doesn't already
exist) and assigns
val1
to
aname[0]
,
val2
to
aname[1]
, etc. As you would
guess, this is more convenient for loading up an array with an initial set of values.
To extract a value from an
array, use the syntax
${
aname
[
i
]}
. For example,
${nicknames[2]}
has the value "bob". The index
i
can be an arithmetic expression-see above.
If you use
*
in place of the index, the value will be all
elements, separated by spaces. Omitting the index is the same as specifying index 0.
Now we come to the somewhat unusual aspect of Korn shell arrays. Assume
that the only values assigned to
nicknames
are the two we saw
above. If you type
print
"
${nicknames[
*
]}"
, you will see the output:
bob ed
In other words,
nicknames[0]
and
nicknames[1]
don't exist. Furthermore, if you were to type:
nicknames[9]=pete
nicknames[31]=ralph
and then type
print
"
${nicknames[
*
]}"
, the output would look like this:
bob ed pete ralph
This is why we said "the elements of
nicknames
with indices 2 and 3" earlier, instead of "the 2nd and 3rd elements of
nicknames
". Any array elements with unassigned values just
don't exist; if you try to access their values, you will get null strings.
You can preserve whatever whitespace you put
in your array elements by using
"
$
{
aname
[@]
}
"
(with the double quotes) instead of
$
{
aname
[
*
]
}
"
, just as you can with
"
$@
"
instead of
$
*
.
The shell provides an operator that tells you
how many elements an array has defined:
${#
aname
[
*
]
}
. Thus
${#nicknames[
*
]
}
has the value 4. Note that
you need the
[
*
]
because the name of the array alone is interpreted as the
0th element. This means, for example, that
${#nicknames}
equals the length of
nicknames[0]
(see
Chapter 4
). Since
nicknames[0]
doesn't exist, the value
of
${#nicknames}
is 0, the length of the null string.
To be quite frank, we feel that the Korn shell's array facility is of
little use to shell programmers. This is partially because it is so limited, but mainly
because shell programming tasks are much more often oriented toward character strings and
text than toward numbers. If you think of an array as a mapping from integers to values
(i.e., put in a number, get out a value), then you can see why arrays are
"number-dominated" data structures.
Nevertheless, we can find useful things to do with arrays.
For example,
here is a cleaner solution to Task 5-4, in which a user can select his or her terminal type
(
TERM
environment variable) at login time.
Recall that the "user-friendly" version of
this code used
select
and a
case
statement:
print 'Select your terminal type:'
PS3='terminal? '
select term in
'Givalt GL35a' \
'Tsoris T-2000' \
'Shande 531' \
'Vey VT99'
do
case $REPLY in
1 ) TERM=gl35a ;;
2 ) TERM=t2000 ;;
3 ) TERM=s531 ;;
4 ) TERM=vt99 ;;
* ) print "invalid." ;;
esac
if [[ -n $term ]]; then
print "TERM is $TERM"
break
fi
done
We can eliminate the entire
case
construct by taking advantage of the fact that the
select
construct stores the user's number choice in the
variable
REPLY
. We just need a line of code that stores all
of the possibilities for
TERM
in an array, in an order that
corresponds to the items in the
select
menu. Then we can use
$REPLY
to index the array. The resulting code is:
set -A termnames gl35a t2000 s531 vt99
print 'Select your terminal type:'
PS3='terminal? '
select term in
'Givalt GL35a' \
'Tsoris T-2000' \
'Shande 531' \
'Vey VT99'
do
if [[ -n $term ]]; then
TERM=${termnames[REPLY-1]}
print "TERM is $TERM"
break
fi
done
This code sets up the array
termnames
so that
${termnames[0]}
is "gl35a",
${termnames[1]}
is "t2000", etc. The line
TERM=${termnames[REPLY-1]}
essentially replaces the entire
case
construct by using
REPLY
to index the array.
Notice that the shell knows to interpret the text in an array index as an
arithmetic expression, as if it were enclosed in
((
and
))
, which in turn means that variable need not be preceded by
a dollar sign (
$
). We have to subtract 1 from the value of
REPLY
because array indices start at 0, while
select
menu item numbers start at 1.
The final Korn shell
feature that relates to the kinds of values that variables can hold is the
typeset
command. If you are a programmer, you might guess
that
typeset
is used to specify the
type
of a variable (integer, string, etc.); you'd be partially right.
typeset
is a rather
ad
hoc
collection of things that you can do to variables that restrict the kinds of
values they can take. Operations are specified by options to
typeset
; the basic syntax is:
typeset
-o varname
[=
value
]
Options can be combined; multiple
varname
s
can be used. If you leave out
varname
, the shell prints a
list of variables for which the given option is turned on.
The options available break down into two basic categories:
String formatting operations, such as right- and left-justification,
truncation, and letter case control.
Type and attribute functions that are of primary interest to advanced
programmers.
typeset
without options has an important meaning: if a
typeset
statement is inside a function definition, then the
variables involved all become
local
to that function (in
addition to any properties they may take on as a result of
typeset
options). The ability to define variables that are local to "subprogram"
units (procedures, functions, subroutines, etc.) is necessary for writing large
programs, because it helps keep subprograms independent of the main program and of each
other.
If you just want to declare a variable local to a function, use
typeset
without any options. For example:
function afunc {
typeset diffvar
samevar=funcvalue
diffvar=funcvalue
print "samevar is $samevar"
print "diffvar is $diffvar"
}
samevar=globvalue
diffvar=globvalue
print "samevar is $samevar"
print "diffvar is $diffvar"
afunc
print "samevar is $samevar"
print "diffvar is $diffvar"
This code will print the following:
samevar is globvalue
diffvar is globvalue
samevar is funcvalue
diffvar is funcvalue
samevar is funcvalue
diffvar is globvalue
The expression $(($OPTIND - 1)) in the last example gives a clue as to how the shell can
do integer arithmetic. As you might guess, the shell interprets words surrounded by $(( and
)) as arithmetic expressions. Variables in arithmetic expressions do not need to be
preceded by dollar signs, though it is not wrong to do so.
Arithmetic expressions are evaluated inside double quotes, like tildes, variables, and command
substitutions. We're finally in a position to state the definitive rule about quoting strings:
When in doubt, enclose a string in single quotes, unless it contains tildes or any expression involving
a dollar sign, in which case you should use double quotes.
date (1) command on System V-derived versions of UNIX accepts arguments that tell it how
to format its output. The argument +%j tells it to print the day of the year, i.e., the number
of days since December 31st of the previous year.
We can use +%j to print a little holiday anticipation message:
print "Only $(( (365-$(date +%j)) / 7 )) weeks until the New Year!"
We'll show where this fits in the overall scheme of command-line processing in Chapter 7, Input/Output
and Command-line Processing .
The arithmetic expression feature is built in to the Korn shell's syntax, and was available in
the Bourne shell (most versions) only through the external command expr (1). Thus it is yet
another example of a desirable feature provided by an external command (i.e., a syntactic kludge)
being better integrated into the shell. [[ / ]] and getopts are also examples of this
design trend.
Korn shell arithmetic expressions are equivalent to their counterparts in the C language. [5]
Precedence and associativity are the same as in C. Table 6.2 shows the arithmetic operators that
are supported. Although some of these are (or contain) special characters, there is no need to backslash-escape
them, because they are within the $(( ... )) syntax.
[5] The assignment forms of these operators are also permitted. For example, $((x += 2))
adds 2 to x and stores the result back in x .
Table 6.2: Arithmetic Operators
Operator
Meaning
+
Plus
-
Minus
*
Times
/
Division (with truncation)
%
Remainder
<<
Bit-shift left
>>
Bit-shift right
&
Bitwise and
|
Bitwise or
~
Bitwise not
^
Bitwise exclusive or
Parentheses can be used to group subexpressions. The arithmetic expression syntax also (like C)
supports relational operators as "truth values" of 1 for true and 0 for false. Table 6.3 shows the
relational operators and the logical operators that can be used to combine relational expressions.
Table 6.3: Relational Operators
Operator
Meaning
<
Less than
>
Greater than
<=
Less than or equal
>=
Greater than or equal
==
Equal
!=
Not equal
&&
Logical and
||
Logical or
For example, $((3 > 2)) has the value 1; $(( (3 > 2) || (4 <= 1) )) also has the
value 1, since at least one of the two subexpressions is true.
The shell also supports base N numbers, where N can be up to 36. The notation
B#N means " N base B ". Of course, if you omit the B
# , the base defaults to 10.
6.2.1 Arithmetic Conditionals
Another construct, closely related to $((...)) , is ((...)) (without the leading
dollar sign). We use this for evaluating arithmetic condition tests, just as [[...]] is used
for string, file attribute, and other types of tests.
((...)) evaluates relational operators differently from $((...)) so that you can
use it in if and while constructs. Instead of producing a textual result, it just sets
its exit status according to the truth of the expression: 0 if true, 1 otherwise. So, for example,
((3 > 2)) produces exit status 0, as does (( (3 > 2) || (4 <= 1) )) , but (( (3
> 2) && (4 <= 1) )) has exit status 1 since the second subexpression isn't true.
You can also use numerical values for truth values within this construct. It's like the analogous
concept in C, which means that it's somewhat counterintuitive to non-C programmers: a value of 0
means false (i.e., returns exit status 1), and a non-0 value means true (returns exit
status 0), e.g., (( 14 )) is true. See the code for the kshdb debugger in Chapter 9
for two more examples of this.
6.2.2 Arithmetic Variables and Assignment
The (( ... )) construct can also be used to define integer variables and assign
values to them. The statement:
(( intvar=expression))
creates the integer variable intvar (if it doesn't already exist) and assigns to it the
result of expression .
That syntax isn't intuitive, so the shell provides a better equivalent: the built-in command
let . The syntax is:
let intvar=expression
It is not necessary (because it's actually redundant) to surround the expression with $((
and )) in a let statement. As with any variable assignment, there must not be any
space on either side of the equal sign ( = ). It is good practice to surround expressions
with quotes, since many characters are treated as special by the shell (e.g., * ,
# , and parentheses); furthermore, you must quote expressions that include whitespace (spaces
or TABs). See Table 6.4 for examples.
Table 6.4: Sample Integer Expression Assignments
Assignment
Value
let x=
$x
1+4
5
' 1 + 4 '
5
' (2+3) * 5 '
25
' 2 + 3 * 5 '
17
' 17 / 3 '
5
' 17 % 3 '
2
' 1<<4 '
16
' 48>>3 '
6
' 17 & 3 '
1
' 17 | 3 '
19
' 17 ^ 3 '
18
Here is a small task that makes use of integer arithmetic.
Task 6.1
Write a script called pages that, given the name of a text file, tells how many pages
of output it contains. Assume that there are 66 lines to a page but provide an option allowing
the user to override that.
We'll make our option -N , a la head . The syntax for this single option
is so simple that we need not bother with getopts . Here is the code:
if [[ $1 = -+([0-9]) ]]; then
let page_lines=${1#-}
shift
else
let page_lines=66
fi
let file_lines="$(wc -l < $1)"
let pages=file_lines/page_lines
if (( file_lines % page_lines > 0 )); then
let pages=pages+1
fi
print "$1 has $pages pages of text."
Notice that we use the integer conditional (( file_lines % page_lines > 0 )) rather than
the [[ ... ]] form.
At the heart of this code is the UNIX utility wc(1) , which counts the number of lines,
words, and characters (bytes) in its input. By default, its output looks something like this:
8 34 161 bob
wc 's output means that the file bob has 8 lines, 34 words, and 161 characters.
wc recognizes the options -l , -w , and -c , which tell it to print only
the number of lines, words, or characters, respectively.
wc normally prints the name of its input file (given as argument). Since we want only the
number of lines, we have to do two things. First, we give it input from file redirection instead,
as in wc -l < bob instead of wc -l bob . This produces the number of lines preceded
by a single space (which would normally separate the filename from the number).
Unfortunately, that space complicates matters: the statement let file_lines=$(wc -l < $1)
becomes "let file_lines= N " after command substitution; the space after the equal sign
is an error. That leads to the second modification, the quotes around the command substitution expression.
The statement let file_lines="N" is perfectly legal, and let knows
how to remove the leading space.
The first if clause in the pages script checks for an option and, if it was given,
strips the dash ( - ) off and assigns it to the variable page_lines . wc in
the command substitution expression returns the number of lines in the file whose name is given as
argument.
The next group of lines calculates the number of pages and, if there is a remainder after the
division, adds 1. Finally, the appropriate message is printed.
As a bigger example of integer arithmetic, we will complete our emulation of the C shell's
pushd and popd functions (Task 4-8). Remember that these functions operate on DIRSTACK
, a stack of directories represented as a string with the directory names separated by spaces.
The C shell's pushd and popd take additional types of arguments, which are:
pushd +n takes the n th directory in the stack (starting with 0), rotates it
to the top, and cd s to it.
pushd without arguments, instead of complaining, swaps the two top directories on the
stack and cd s to the new top.
popd +n takes the n th directory in the stack and just deletes it.
The most useful of these features is the ability to get at the n th directory in the stack.
Here are the latest versions of both functions:
function pushd { # push current directory onto stack
dirname=$1
if [[ -d $dirname && -x $dirname ]]; then
cd $dirname
DIRSTACK="$dirname ${DIRSTACK:-$PWD}"
print "$DIRSTACK"
else
print "still in $PWD."
fi
}
function popd { # pop directory off the stack, cd to new top
if [[ -n $DIRSTACK ]]; then
DIRSTACK=${DIRSTACK#* }
cd ${DIRSTACK%% *}
print "$PWD"
else
print "stack empty, still in $PWD."
fi
}
To get at the n th directory, we use a while loop that transfers the top directory
to a temporary copy of the stack n times. We'll put the loop into a function called getNdirs
that looks like this:
function getNdirs{
stackfront=''
let count=0
while (( count < $1 )); do
stackfront="$stackfront ${DIRSTACK%% *}"
DIRSTACK=${DIRSTACK#* }
let count=count+1
done
}
The argument passed to getNdirs is the n in question. The variable stackfront
is the temporary copy that will contain the first n directories when the loop is done.
stackfront starts as null; count , which counts the number of loop iterations, starts
as 0.
The first line of the loop body appends the top of the stack ( ${DIRSTACK%%*
} ) to stackfront ; the second line deletes the top from the stack. The last
line increments the counter for the next iteration. The entire loop executes N times, for
values of count from 0 to N -1.
When the loop finishes, the last directory in $stackfront is the N th directory.
The expression ${stackfront##* } extracts this directory. Furthermore,
DIRSTACK now contains the "back" of the stack, i.e., the stack without the first
n directories. With this in mind, we can now write the code for the improved versions of pushd
and popd :
function pushd {
if [[ $1 = ++([0-9]) ]]; then
# case of pushd +n: rotate n-th directory to top
let num=${1#+}
getNdirs $num
newtop=${stackfront##* }
stackfront=${stackfront%$newtop}
DIRSTACK="$newtop $stackfront $DIRSTACK"
cd $newtop
elif [[ -z $1 ]]; then
# case of pushd without args; swap top two directories
firstdir=${DIRSTACK%% *}
DIRSTACK=${DIRSTACK#* }
seconddir=${DIRSTACK%% *}
DIRSTACK=${DIRSTACK#* }
DIRSTACK="$seconddir $firstdir $DIRSTACK"
cd $seconddir
else
cd $dirname
# normal case of pushd dirname
dirname=$1
if [[ -d $dirname && -x $dirname ]]; then
DIRSTACK="$dirname ${DIRSTACK:-$PWD}"
print "$DIRSTACK"
else
print still in "$PWD."
fi
fi
}
function popd { # pop directory off the stack, cd to new top
if [[ $1 = ++([0-9]) ]]; then
# case of popd +n: delete n-th directory from stack
let num={$1#+}
getNdirs $num
stackfront=${stackfront% *}
DIRSTACK="$stackfront $DIRSTACK"
else
# normal case of popd without argument
if [[ -n $DIRSTACK ]]; then
DIRSTACK=${DIRSTACK#* }
cd ${DIRSTACK%% *}
print "$PWD"
else
print "stack empty, still in $PWD."
fi
fi
}
These functions have grown rather large; let's look at them in turn. The if at the beginning
of pushd checks if the first argument is an option of the form +N . If so,
the first body of code is run. The first let simply strips the plus sign (+) from the argument
and assigns the result - as an integer - to the variable num . This, in turn, is passed to
the getNdirs function.
The next two assignment statements set newtop to the N th directory - i.e., the
last directory in $stackfront - and delete that directory from stackfront . The final
two lines in this part of pushd put the stack back together again in the appropriate order
and cd to the new top directory.
The elif clause tests for no argument, in which case pushd should swap the top two
directories on the stack. The first four lines of this clause assign the top two directories to
firstdir and seconddir , and delete these from the stack. Then, as above, the code
puts the stack back together in the new order and cd s to the new top directory.
The else clause corresponds to the usual case, where the user supplies a directory name
as argument.
popd works similarly. The if clause checks for the +N option, which
in this case means delete the N th directory. A let extracts the N as an integer;
the getNdirs function puts the first n directories into stackfront . Then the
line stackfront=${stackfront% *} deletes the last directory (the N th directory) from
stackfront . Finally, the stack is put back together with the N th directory missing.
The else clause covers the usual case, where the user doesn't supply an argument.
Before we leave this subject, here are a few exercises that should test your understanding of
this code:
Add code to pushd that exits with an error message if the user supplies no argument
and the stack contains fewer than two directories.
Verify that when the user specifies +N and N exceeds the number of directories
in the stack, both pushd and popd use the last directory as the N th directory.
Modify the getNdirs function so that it checks for the above condition and exits with
an appropriate error message if true.
Change getNdirs so that it uses cut (with command substitution), instead of the
while loop, to extract the first N directories. This uses less code but runs more
slowly because of the extra processes generated.
# MS-DOS / XP cmd like stuff
alias edit = $VISUAL
alias copy = 'cp'
alias cls = 'clear'
alias del = 'rm'
alias dir = 'ls'
alias md = 'mkdir'
alias move = 'mv'
alias rd = 'rmdir'
alias ren = 'mv'
alias ipconfig = 'ifconfig'
The variable CDPATH defines the search path for the directory containing directories. So it served much like "directories
home". The dangers are in creating too complex CDPATH. Often a single directory works best. For example export CDPATH = /srv/www/public_html
. Now, instead of typing cd /srv/www/public_html/CSS I can simply type: cd CSS
Use CDPATH to access frequent directories in bash
Mar 21, '05 10:01:00AM • Contributed by:
jonbauman
I often find myself wanting to cd to the various directories beneath my home directory (i.e. ~/Library, ~/Music, etc.),
but being lazy, I find it painful to have to type the ~/ if I'm not in my home directory already. Enter CDPATH
, as desribed in man bash ):
The search path for the cd command. This is a colon-separated list of directories in which the shell looks for destination
directories specified by the cd command. A sample value is ".:~:/usr".
Personally, I use the following command (either on the command line for use in just that session, or in .bash_profile
for permanent use):
CDPATH=".:~:~/Library"
This way, no matter where I am in the directory tree, I can just cd dirname , and it will take me to the directory that
is a subdirectory of any of the ones in the list. For example:
$ cd
$ cd Documents
/Users/baumanj/Documents
$ cd Pictures
/Users/username/Pictures
$ cd Preferences
/Users/username/Library/Preferences
etc...
[ robg adds: No, this isn't some deeply buried treasure of OS X, but I'd never heard of the CDPATH variable, so
I'm assuming it will be of interest to some other readers as well.]
cdable_vars is also nice
Authored by: clh on Mar 21, '05 08:16:26PM
Check out the bash command shopt -s cdable_vars
From the man bash page:
cdable_vars
If set, an argument to the cd builtin command that is not a directory is assumed to be the name of a variable whose value
is the directory to change to.
With this set, if I give the following bash command:
export d="/Users/chap/Desktop"
I can then simply type
cd d
to change to my Desktop directory.
I put the shopt command and the various export commands in my .bashrc file.
For privacy of my data I wanted to lock down /downloads on my file server. So I
ran:
chmod
0000
/
downloads
chmod 0000 /downloads
The root user can still has access and ls and cd commands will not work. To go
back:
chmod
0755
/
downloads
chmod 0755 /downloads
Clear gibberish all over the screen
Just type:
reset
reset
Becoming human
Pass the
-h
or
-H
(and other options) command line option
to GNU or BSD utilities to get output of command commands like ls, df, du, in
human-understandable formats:
ls
-lh
# print sizes in human readable format (e.g., 1K 234M 2G)
df
-h
df
-k
# show output in bytes, KB, MB, or GB
free
-b
free
-k
free
-m
free
-g
# print sizes in human readable format (e.g., 1K 234M 2G)
du
-h
# get file system perms in human readable format
stat
-c
%
A
/
boot
# compare human readable numbers
sort
-h
-a
file
# display the CPU information in human readable format on a Linux
lscpu
lscpu
-e
lscpu
-e
=cpu,node
# Show the size of each file but in a more human readable way
tree
-h
tree
-h
/
boot
ls -lh # print sizes in human readable
format (e.g., 1K 234M 2G) df -h df -k # show output in bytes, KB, MB, or GB
free -b free -k free -m free -g # print sizes in human readable format (e.g.,
1K 234M 2G) du -h # get file system perms in human readable format stat -c %A
/boot # compare human readable numbers sort -h -a file # display the CPU
information in human readable format on a Linux lscpu lscpu -e lscpu -e=cpu,node
# Show the size of each file but in a more human readable way tree -h tree -h
/boot
Show information about known users in the Linux based system
Just type:
## linux version ##
lslogins
## BSD version ##
logins
## linux version ## lslogins## BSD
version ## logins
Sample outputs:
UID USER PWD-LOCK PWD-DENY LAST-LOGIN GECOS
0 root 0 0 22:37:59 root
1 bin 0 1 bin
2 daemon 0 1 daemon
3 adm 0 1 adm
4 lp 0 1 lp
5 sync 0 1 sync
6 shutdown 0 1 2014-Dec17 shutdown
7 halt 0 1 halt
8 mail 0 1 mail
10 uucp 0 1 uucp
11 operator 0 1 operator
12 games 0 1 games
13 gopher 0 1 gopher
14 ftp 0 1 FTP User
27 mysql 0 1 MySQL Server
38 ntp 0 1
48 apache 0 1 Apache
68 haldaemon 0 1 HAL daemon
69 vcsa 0 1 virtual console memory owner
72 tcpdump 0 1
74 sshd 0 1 Privilege-separated SSH
81 dbus 0 1 System message bus
89 postfix 0 1
99 nobody 0 1 Nobody
173 abrt 0 1
497 vnstat 0 1 vnStat user
498 nginx 0 1 nginx user
499 saslauth 0 1 "Saslauthd user"
Confused on a top command output?
Seriously, you need to try out htop instead of top:
sudo
htop
sudo htop
Want to run the same command again?
Just type
!!
. For example:
/
myhome
/
dir
/
script
/
name arg1 arg2
# To run the same command again
!!
## To run the last command again as root user
sudo
!!
/myhome/dir/script/name arg1 arg2# To
run the same command again !!## To run the last command again as root user sudo
!!
The
!!
repeats the most recent command. To run the most recent
command beginning with "foo":
!
foo
# Run the most recent command beginning with "service" as root
sudo
!
service
!foo # Run the most recent command
beginning with "service" as root sudo !service
The
!$
use to run command with the last argument of the most recent
command:
# Edit nginx.conf
sudo
vi
/
etc
/
nginx
/
nginx.conf
# Test nginx.conf for errors
/
sbin
/
nginx
-t
-c
/
etc
/
nginx
/
nginx.conf
# After testing a file with "/sbin/nginx -t -c /etc/nginx/nginx.conf", you
# can edit file again with vi
sudo
vi
!
$
# Edit nginx.conf sudo vi
/etc/nginx/nginx.conf# Test nginx.conf for errors /sbin/nginx -t -c
/etc/nginx/nginx.conf# After testing a file with "/sbin/nginx -t -c
/etc/nginx/nginx.conf", you # can edit file again with vi sudo vi !$
Get a reminder you when you have to leave
If you need a reminder to leave your terminal, type the following command:
leave +hhmm
leave +hhmm
Where,
hhmm
– The time of day is in the form hhmm where hh is a time in
hours (on a 12 or 24 hour clock), and mm are minutes. All times are converted
to a 12 hour clock, and assumed to be in the next 12 hours.
Home sweet home
Want to go the directory you were just in? Run:
cd -
Need to quickly return to your home directory? Enter:
cd
The variable
CDPATH
defines the search path for the directory
containing directories:
export
CDPATH
=
/
var
/
www:
/
nas10
export CDPATH=/var/www:/nas10
Now, instead of typing
cd /var/www/html/
I can simply type the
following to cd into /var/www/html path:
cd
html
cd html
Editing a file being viewed with less pager
To edit a file being viewed with less pager, press
v
. You will have
the file for edit under $EDITOR:
less
*
.c
less
foo.html
## Press v to edit file ##
## Quit from editor and you would return to the less pager again ##
less *.c less foo.html ## Press v to
edit file ## ## Quit from editor and you would return to the less pager again
##
List all files or directories on your system
To see all of the directories on your system, run:
find
/
-type
d
|
less
# List all directories in your $HOME
find
$HOME
-type
d
-ls
|
less
find / -type d | less# List all
directories in your $HOME find $HOME -type d -ls | less
To see all of the files, run:
find
/
-type
f
|
less
# List all files in your $HOME
find
$HOME
-type
f
-ls
|
less
find / -type f | less# List all files
in your $HOME find $HOME -type f -ls | less
Build directory trees in a single command
You can create directory trees one at a time using mkdir command by passing the
-p
option:
Reading rear sources is an interesting exercise. It really demonstrates attempt to use
"reasonable' style of shell programming and you can learn a lot.
Here is a collection of coding hints that should help to get a more consistent code base.
Don't be afraid to contribute to Relax-and-Recover even if your contribution does not fully match
all this coding hints. Currently large parts of the Relax-and-Recover code are not yet in compliance
with this coding hints. This is an ongoing step by step process. Nevertheless try to understand the
idea behind this coding hints so that you know how to break them properly (i.e. "learn the rules
so you know how to break them properly").
Variables and functions must have names that explain what they do, even if it makes them longer.
Avoid too short names, in particular do not use one-letter-names (like a variable named
i - just try to 'grep' for it over the whole code to find code that is related to
i ). In general names should consist of two parts, a generic part plus a specific part
to make them meaningful. For example dev is basically meaningless because there are
so many different kind of device-like thingies. Use names like boot_dev or even better
boot_partition versus bootloader_install_device to make it unambiguous
what that thingy actually is about. Use different names for different things so that others can
'grep' over the whole code and get a correct overview what actually belongs to a particular name.
Introduce intermediate variables with meaningful names to tell what is going on.
For example instead of running commands with obfuscated arguments like rm -f $( ls ... | sed ... | grep ... | awk ... )
which looks scaring (what the heck gets deleted here?) better use
that tells the intent behind (regardless whether or not that code is the best way to do it - but
now others can easily improve it).
Use functions to structure longer programs into code blocks that can be understood independently.
Don't use || and && one-liners, write proper if-then-else-fi blocks.
Exceptions are simple do-or-die statements like COMMAND || Error "meaningful error message"
and only if it aids readability compared to a full if-then-else clause.
Use $( COMMAND ) instead of backticks `COMMAND`
Use spaces when possible to aid readability like output=( $( COMMAND1 OPTION1 | COMMAND2 OPTION2 ) )
instead of output=($(COMMAND1 OPTION1|COMMAND2 OPTION2))
Do not only tell what the code does (i.e. the implementation details) but also explain what the
intent behind is (i.e. why ) to make the code maintainable.
Provide meaningful comments that tell what the computer should do and also explain why it
should do it so that others understand the intent behind so that they can properly fix issues
or adapt and enhance it as needed.
If there is a GitHub issue or another URL available for a particular piece of code provide
a comment with the GitHub issue or any other URL that tells about the reasoning behind current
implementation details.
Here the initial example so that one can understand what it is about:
#!/bin/bash # output the first N square numbers # by summing up the first N odd numbers 1 3 ...
2*N-1 # where each nth partial sum is the nth square number # see https://en.wikipedia.org/wiki/Square_number#Properties
# this way it is a little bit faster for big N compared to # calculating each square number on its
own via multiplication N=$1 if ! [[ $N =~ ^[0-9]+$ ]] ; then echo "Input must be non-negative integer."
1>&2 exit 1 fi square_number=0 for odd_number in $( seq 1 2 $(( 2 * N - 1 )) ) ; do (( square_number
+= odd_number )) && echo $square_number done
Now the intent behind is clear and now others can easily decide if that code is really the best
way to do it and easily improve it if needed.
By default bash proceeds with the next command when something failed. Do not let your code blindly
proceed in case of errors because that could make it hard to find the root cause of a failure when
it errors out somewhere later at an unrelated place with a weird error message which could lead to
false fixes that cure only a particular symptom but not the root cause.
In case of errors better abort than to blindly proceed.
At least test mandatory conditions before proceeding. If a mandatory condition is not fulfilled
abort with Error "meaningful error message" , see 'Relax-and-Recover functions' below.
Preferably in new scripts use set -ue to die from unset variables and unhandled
errors and use set -o pipefail to better notice failures in a pipeline. When leaving
the script restore the Relax-and-Recover default bash flags and options with
Implement adaptions and enhancements in a backward compatible way so that your changes do not
cause regressions for others.
One same Relax-and-Recover code must work on various different systems. On older systems as
well as on newest systems and on various different Linux distributions.
Preferably use simple generic functionality that works on any Linux system. Better very simple
code than oversophisticated (possibly fragile) constructs. In particular avoid special bash version
4 features (Relax-and-Recover code should also work with bash version 3).
When there are incompatible differences on different systems distinction of cases with separated
code is needed because it is more important that the Relax-and-Recover code works everywhere than
having generic code that sometimes fails.
When there are special issues on particular systems it is more important that the Relax-and-Recover
code works than having nice looking clean code that sometimes fails. In such special cases any dirty
hacks that intend to make it work everywhere are welcome. But for dirty hacks the above listed coding
hints become mandatory rules:
Provide explanatory comments that tell what a dirty hack does together with a GitHub issue
or any other URL that tell about the reasoning behind the dirty hack to enable others to properly
adapt or clean up a dirty hack at any time later when the reason for it had changed or gone away.
Try as good as you can to foresee possible errors or failures of a dirty hack and error out
with meaningful error messages if things go wrong to enable others to understand the reason behind
a failure.
Implement the dirty hack in a way so that it does not cause regressions for others.
For example a dirty hack like the following is perfectly acceptable:
# FIXME: Dirty hack to make it work # on "FUBAR Linux version 666" # where COMMAND sometimes inexplicably
fails # but always works after at most 3 attempts # see http://example.org/issue12345 # Retries should
have no bad effect on other systems # where the first run of COMMAND works. COMMAND || COMMAND ||
COMMAND || Error "COMMAND failed."
Use only traditional (7-bit) ASCII charactes. In particular do not use UTF-8 encoded multi-byte
characters.
Non-ASCII characters in scripts may cause arbitrary unexpected failures on systems that do
not support other locales than POSIX/C. During "rear recover" only the POSIX/C locale works (the
ReaR rescue/recovery system has no support for non-ASCII locales) and /usr/sbin/rear sets the
C locale so that non-ASCII characters are invalid in scripts. Have in mind that basically all
files in ReaR are scripts. E.g. also /usr/share/rear/conf/default.conf and /etc/rear/local.conf
are sourced (and executed) as scripts.
English documentation texts do not need non-ASCII characters. Using non-ASCII characters in
documentation texts makes it needlessly hard to display the documentation correctly for any user
on any system. When non-ASCII characters are used but the user does not have the exact right matching
locale set arbitrary nonsense can happen, cf.
https://en.opensuse.org/SDB:Plain_Text_versus_Locale
Use the available Relax-and-Recover functions when possible instead of re-implementing basic functionality
again and again. The Relax-and-Recover functions are implemented in various
lib/*-functions.sh
files .
is_true and is_false :
See
lib/global-functions.sh how to use them.
For example instead of using if [[ ! "$FOO" =~ ^[yY1] ]] ; then
use if ! is_true "$FOO" ; then
Use paired parenthesis for case patterns as in case WORD in (PATTERN) COMMANDS ;; esac
so that editor commands (like '%' in 'vi') that check for matching opening and closing parenthesis
work everywhere in the code.
A very nice tutorial by Vivek Gite (created October 31, 2008 last updated June 24, 2015).
His mistake is putting new for loop too far inside the tutorial. It should emphazied, not hidden.
Bash v4.0+ has inbuilt support for setting up a step value using {START..END..INCREMENT}
syntax:
#!/bin/bash
echo "Bash version ${BASH_VERSION}..."
for i in {0..10..2}
do
echo "Welcome $i times"
done
Sample outputs:
Bash version 4.0.33(0)-release...
Welcome 0 times
Welcome 2 times
Welcome 4 times
Welcome 6 times
Welcome 8 times
Welcome 10 times
... ... ...
Three-expression bash for loops syntax
This type of for loop share a common heritage with the C programming language. It is characterized
by a three-parameter loop control expression; consisting of an initializer (EXP1), a loop-test or
condition (EXP2), and a counting expression (EXP3).
for (( EXP1; EXP2; EXP3 ))
do
command1
command2
command3
done
A representative three-expression example in bash as follows:
#!/bin/bash
for (( c=1; c<=5; c++ ))
do
echo "Welcome $c times"
done
... ... ...
Jadu Saikia, November 2, 2008, 3:37 pm
Nice one. All the examples are explained well, thanks Vivek.
seq 1 2 20
output can also be produced using jot
jot – 1 20 2
The infinite loops as everyone knows have the following alternatives.
while(true)
or
while :
//Jadu
Andi Reinbrech, November 18, 2010, 7:42 pm
I know this is an ancient thread, but thought this trick might be helpful to someone:
For the
above example with all the cuts, simply do
set `echo $line`
This will split line into positional parameters and you can after the set simply say
F1=$1; F2=$2; F3=$3
I used this a lot many years ago on solaris with "set `date`", it neatly splits the whole date
string into variables and saves lots of messy cutting :-)
… no, you can't change the FS, if it's not space, you can't use this method
Peko, July 16, 2009, 6:11 pm
Hi Vivek,
Thanks for this a useful topic.
IMNSHO, there may be something to modify here
=======================
Latest bash version 3.0+ has inbuilt support for setting up a step value:
#!/bin/bash
for i in {1..5}
=======================
1) The increment feature seems to belong to the version 4 of bash.
Reference:
http://bash-hackers.org/wiki/doku.php/syntax/expansion/brace
Accordingly, my bash v3.2 does not include this feature.
BTW, where did you read that it was 3.0+ ?
(I ask because you may know some good website of interest on the subject).
2) The syntax is {from..to..step} where from, to, step are 3 integers.
You code is missing the increment.
Note that GNU Bash documentation may be bugged at this time,
because on GNU Bash manual, you will find the syntax {x..y[incr]}
which may be a typo. (missing the second ".." between y and increment).
Keep on the good work of your own,
Thanks a million.
- Peko
Michal Kaut July 22, 2009, 6:12 am
Hello,
is there a simple way to control the number formatting? I use several computers, some
of which have non-US settings with comma as a decimal point. This means that for x in $(seq 0 0.1 1) gives 0 0.1 0.2 … 1 one some machines and 0 0,1 0,2 … 1 on
other.
Is there a way to force the first variant, regardless of the language settings? Can I, for example,
set the keyboard to US inside the script? Or perhaps some alternative to $x that
would convert commas to points?
(I am sending these as parameters to another code and it won't accept numbers with commas…)
The best thing I could think of is adding x=`echo $x | sed s/,/./` as a first
line inside the loop, but there should be a better solution? (Interestingly, the sed command does
not seem to be upset by me rewriting its variable.)
Thanks,
Michal
Peko July 22, 2009, 7:27 am
To Michal Kaut:
Hi Michal,
Such output format is configured through LOCALE settings.
I tried :
export LC_CTYPE="en_EN.UTF-8″; seq 0 0.1 1
and it works as desired.
You just have to find the exact value for LC_CTYPE that fits to your systems and your needs.
Peko
Peko July 22, 2009, 2:29 pm
To Michal Kaus [2]
Ooops – ;-)
Instead of LC_CTYPE,
LC_NUMERIC should be more appropriate
(Although LC_CTYPE is actually yielding to the same result – I tested both)
To Vivek:
Regarding your last example, that is : running a loop through arguments given to the script on
the command line, there is a simplier way of doing this:
# instead of:
# FILES="$@"
# for f in $FILES
# use the following syntax
for arg
do
# whatever you need here – try : echo "$arg"
done
Of course, you can use any variable name, not only "arg".
Philippe Petrinko November 11, 2009, 11:25 am
To tdurden:
Why would'nt you use
1) either a [for] loop
for old in * ; do mv ${old} ${old}.new; done
2) Either the [rename] command ?
excerpt form "man rename" :
DESCRIPTION
"rename" renames the filenames supplied according to the rule specified
as the first argument. The perlexpr argument is a Perl expression
which is expected to modify the $_ string in Perl for at least some of
the filenames specified. If a given filename is not modified by the
expression, it will not be renamed. If no filenames are given on the
command line, filenames will be read via standard input.
For example, to rename all files matching "*.bak" to strip the
extension, you might say
rename 's/\.bak$//' *.bak
To translate uppercase names to lower, you'd use
rename 'y/A-Z/a-z/' *
- Philippe
Philippe Petrinko November 11, 2009, 9:27 pm
If you set the shell option extglob, Bash understands some more powerful patterns. Here, a
is one or more pattern, separated by the pipe-symbol (|).
?() Matches zero or one occurrence of the given patterns
*() Matches zero or more occurrences of the given patterns
+() Matches one or more occurrences of the given patterns
@() Matches one of the given patterns
!() Matches anything except one of the given patterns
To Sean:
Right, the more sharp a knife is, the easier it can cut your fingers…
I mean: There are side-effects to the use of file globbing (like in [ for f in * ] ) , when
the globbing expression matches nothing: the globbing expression is not susbtitued.
There is an interesting difference between the exit value for two different for looping structures
(hope this comes out right): for (( c=1; c<=2; c++ )) do echo -n "inside (( )) loop c is $c, "; done; echo "done (( ))
loop c is $c"
for c in {1..2}; do echo -n "inside { } loop c is $c, "; done; echo "done { } loop c is $c"
You see that the first structure does a final increment of c, the second does not. The first is
more useful IMO because if you have a conditional break in the for loop, then you can subsequently
test the value of $c to see if the for loop was broken or not; with the second structure you can't
know whether the loop was broken on the last iteration or continued to completion.
Dominic January 14, 2010, 10:09 am
sorry, my previous post would have been clearer if I had shown the output of my code snippet,
which is: inside (( )) loop c is 1, inside (( )) loop c is 2, done (( )) loop c is 3
inside { } loop c is 1, inside { } loop c is 2, done { } loop c is 2
Philippe Petrinko March 9, 2010, 2:34 pm
@Dmitry
And, again, as stated many times up there, using [seq] is counter productive, because it requires
a call to an external program, when you should Keep It Short and Simple, using only bash internals
functions:
for ((c=1; c<21; c+=2)); do echo "Welcome $c times" ; done
(and I wonder why Vivek is sticking to that old solution which should be presented only for
historical reasons when there was no way of using bash internals.
By the way, this historical recall should be placed only at topic end, and not on top of the topic,
which makes newbies sticking to the not-up-to-date technique ;-) )
Sean March 9, 2010, 11:15 pm
I have a comment to add about using the builtin for (( … )) syntax. I would agree the
builtin method is cleaner, but from what I've noticed with other builtin functionality, I had
to check the speed advantage for myself. I wrote the following files:
builtin_count.sh:
#!/bin/bash
for ((i=1;i<=1000000;i++))
do
echo "Output $i"
done
seq_count.sh:
#!/bin/bash
for i in $(seq 1 1000000)
do
echo "Output $i"
done
And here were the results that I got:
time ./builtin_count.sh
real 0m22.122s
user 0m18.329s
sys 0m3.166s
time ./seq_count.sh
real 0m19.590s
user 0m15.326s
sys 0m2.503s
The performance increase isn't too significant, especially when you are probably going to be
doing something a little more interesting inside of the for loop, but it does show that builtin
commands are not necessarily faster.
Andi Reinbrech November 18, 2010, 8:35 pm
The reason why the external seq is faster, is because it is executed only once, and returns
a huge splurb of space separated integers which need no further processing, apart from the for
loop advancing to the next one for the variable substitution.
The internal loop is a nice and clean/readable construct, but it has a lot of overhead. The
check expression is re-evaluated on every iteration, and a variable on the interpreter's heap
gets incremented, possibly checked for overflow etc. etc.
Note that the check expression cannot be simplified or internally optimised by the interpreter
because the value may change inside the loop's body (yes, there are cases where you'd want to
do this, however rare and stupid they may seem), hence the variables are volatile and get re-evaluted.
I.e. botom line, the internal one has more overhead, the "seq" version is equivalent to either
having 1000000 integers inside the script (hard coded), or reading once from a text file with
1000000 integers with a cat. Point being that it gets executed only once and becomes static.
OK, blah blah fishpaste, past my bed time :-)
Cheers,
Andi
Anthony Thyssen June 4, 2010, 6:53 am
The {1..10} syntax is pretty useful as you can use a variable with it!
TheBonsai wrote…. "The seq-function above could use i=$((i + inc)), if only POSIX matters.
expr is obsolete for those things, even in POSIX."
I am not certain it is in Posix. It was NOT part of the original Bourne Shell, and on some
machines, I deal with Bourne Shell. Not Ksh, Bash, or anything else.
Bourne Shell syntax works everywhere! But as 'expr' is a builtin in more modern shells, then
it is not a big loss or slow down.
This is especially important if writing a replacement command, such as for "seq" where you
want your "just-paste-it-in" function to work as widely as possible.
I have been shell programming pretty well all the time since 1988, so I know what I am talking
about! Believe me.
MacOSX has in this regard been the worse, and a very big backward step in UNIX compatibility.
2 year after it came out, its shell still did not even understand most of the normal 'test' functions.
A major pain to write shells scripts that need to also work on this system.
TheBonsai June 6, 2010, 12:35 pm
Yea, the question was if it's POSIX, not if it's 100% portable (which is a difference). The
POSIX base more or less is a subset of the Korn features (88, 93), pure Bourne is something "else",
I know. Real portability, which means a program can go wherever UNIX went, only in C ;)
Philippe Petrinko November 22, 2010, 8:23 am
And if you want to get rid of double-quotes, use:
one-liner code: while read; do record=${REPLY}; echo ${record}|while read -d ","; do field="${REPLY#\"}";
field="${field%\"}"; echo ${field}; done; done<data
script code, added of some text to better see record and field breakdown:
#!/bin/bash
while read
do
echo "New record"
record=${REPLY}
echo ${record}|while read -d ,
do
field="${REPLY#\"}"
field="${field%\"}"
echo "Field is :${field}:"
done
done<data
Does it work with your data?
- PP
Philippe Petrinko November 22, 2010, 9:01 am
Of course, all the above code was assuming that your CSV file is named "data".
If you want to use anyname with the script, replace:
done<data
With:
done
And then use your script file (named for instance "myScript") with standard input redirection:
myScript < anyFileNameYouWant
Enjoy!
Philippe Petrinko November 22, 2010, 11:28 am
well no there is a bug, last field of each record is not read – it needs a workout and may
be IFS modification ! After all that's what it was built for… :O)
Anthony Thyssen November 22, 2010, 11:31 pm
Another bug is the inner loop is a pipeline, so you can't assign variables for use later in
the script. but you can use '<<<' to break the pipeline and avoid the echo.
But this does not help when you have commas within the quotes! Which is why you needed quotes
in the first place.
In any case It is a little off topic. Perhaps a new thread for reading CVS files in shell should
be created.
Philippe Petrinko November 24, 2010, 6:29 pm
Anthony,
Would you try this one-liner script on your CSV file?
This one-liner assumes that CSV file named [data] has __every__ field double-quoted.
while read; do r="${REPLY#\"}";echo "${r//\",\"/\"}"|while read -d \";do echo "Field is :${REPLY}:";done;done<data
Here is the same code, but for a script file, not a one-liner tweak.
#!/bin/bash
# script csv01.sh
#
# 1) Usage
# This script reads from standard input
# any CSV with double-quoted data fields
# and breaks down each field on standard output
#
# 2) Within each record (line), _every_ field MUST:
# - Be surrounded by double quotes,
# - and be separated from preceeding field by a comma
# (not the first field of course, no comma before the first field)
#
while read
do
echo "New record" # this is not mandatory-just for explanation
#
#
# store REPLY and remove opening double quote
record="${REPLY#\"}"
#
#
# replace every "," by a single double quote
record=${record//\",\"/\"}
#
#
echo ${record}|while read -d \"
do
# store REPLY into variable "field"
field="${REPLY}"
#
#
echo "Field is :${field}:" # just for explanation
done
done
This script named here [cvs01.sh] must be used so:
cvs01.sh < my-cvs-file-with-doublequotes
Philippe Petrinko November 24, 2010, 6:35 pm
@Anthony,
By the way, using [REPLY] in the outer loop _and_ the inner loop is not a bug.
As long as you know what you do, this is not problem, you just have to store [REPLY] value conveniently,
as this script shows.
TheBonsai March 8, 2011, 6:26 am
for ((i=1; i<=20; i++)); do printf "%02d\n" "$i"; done
+1 for printf due to portability, but you can use bashy .. syntax too
for i in {01..20}; do echo "$i"; done
TheBonsai March 8, 2011, 6:48 am
Well, it isn't portable per se, it makes it portable to pre-4 Bash versions.
I think a more or less "portable" (in terms of POSIX, at least) code would be
i=0
while [ "$((i >= 20))" -eq 0 ]; do
printf "%02d\n" "$i"
i=$((i+1))
done
Philip Ratzsch April 20, 2011, 5:53 am
I didn't see this in the article or any of the comments so I thought I'd share. While this
is a contrived example, I find that nesting two groups can help squeeze a two-liner (once for
each range) into a one-liner:
for num in {{1..10},{15..20}};do echo $num;done
Great reference article!
Philippe Petrinko April 20, 2011, 8:23 am
@Philip
Nice thing to think of, using brace nesting, thanks for sharing.
Philippe Petrinko May 6, 2011, 10:13 am
Hello Sanya,
That would be because brace expansion does not support variables. I have to check this.
Anyway, Keep It Short and Simple: (KISS) here is a simple solution I already gave above:
xstart=1;xend=10;xstep=1
for (( x = $xstart; x <= $xend; x += $xstep)); do echo $x;done
Actually, POSIX compliance allows to forget $ in for quotes, as said before, you could also
write:
xstart=1;xend=10;xstep=1
for (( x = xstart; x <= xend; x += xstep)); do echo $x;done
Philippe Petrinko May 6, 2011, 10:48 am
Sanya,
Actually brace expansion happens __before__ $ parameter exapansion, so you cannot use it this
way.
Nevertheless, you could overcome this this way:
max=10; for i in $(eval echo {1..$max}); do echo $i; done
Sanya May 6, 2011, 11:42 am
Hello, Philippe
Thanks for your suggestions
You basically confirmed my findings, that bash constructions are not as simple as zsh ones.
But since I don't care about POSIX compliance, and want to keep my scripts "readable" for less
experienced people, I would prefer to stick to zsh where my simple for-loop works
Cheers, Sanya
Philippe Petrinko May 6, 2011, 12:07 pm
Sanya,
First, you got it wrong: solutions I gave are not related to POSIX, I just pointed out that
POSIX allows not to use $ in for (( )), which is just a little bit more readable – sort of.
Second, why do you see this less readable than your [zsh] [for loop]?
for (( x = start; x <= end; x += step)) do
echo "Loop number ${x}"
done
It is clear that it is a loop, loop increments and limits are clear.
IMNSHO, if anyone cannot read this right, he should not be allowed to code. :-D
BFN
Anthony Thyssen May 8, 2011, 11:30 pm
If you are going to do… $(eval echo {1..$max});
You may as well use "seq" or one of the many other forms.
See all the other comments on doing for loops.
Tom P May 19, 2011, 12:16 pm
I am trying to use the variable I set in the for line on to set another variable with a different
extension. Couldn't get this to work and couldnt find it anywhere on the web… Can someone help.
Example:
FILE_TOKEN=`cat /tmp/All_Tokens.txt`
for token in $FILE_TOKEN
do
A1_$token=`grep $A1_token /file/path/file.txt | cut -d ":" -f2`
my goal is to take the values from the ALL Tokens file and set a new variable with A1_ infront
of it… This tells be that A1_ is not a command…
Many people hack together shell scripts quickly to do simple tasks, but these
soon take on a life of their own. Unfortunately shell scripts are full of subtle
effects which result in scripts failing in unusual ways. It's possible to write
scripts which minimise these problems. In this article, I explain several techniques
for writing robust bash scripts.
Use set -u
How often have you written a script that broke because a variable wasn't
set? I know I have, many times.
chroot=$1
...
rm -rf $chroot/usr/share/doc
If you ran the script above and accidentally forgot to give a parameter,
you would have just deleted all of your system documentation rather than making
a smaller chroot. So what can you do about it? Fortunately bash provides you
with set -u, which will exit your script if you try to use an uninitialised
variable. You can also use the slightly more readable set -o nounset.
Every script you write should include set -e at the top. This tells
bash that it should exit the script if any statement returns a non-true return
value. The benefit of using -e is that it prevents errors snowballing into serious
issues when they could have been caught earlier. Again, for readability you
may want to use set -o errexit.
Using -e gives you error checking for free. If you forget to check something,
bash will do it or you. Unfortunately it means you can't check $? as bash will
never get to the checking code if it isn't zero. There are other constructs
you could use:
command
if [ "$?"-ne 0]; then echo "command failed"; exit 1; fi
could be replaced with
command || { echo "command failed"; exit 1; }
or
if ! command; then echo "command failed"; exit 1; fi
What if you have a command that returns non-zero or you are not interested
in its return value? You can use command || true, or if you have a
longer section of code, you can turn off the error checking, but I recommend
you use this sparingly.
set +e
command1
command2
set -e
On a slightly related note, by default bash takes the error status of the
last item in a pipeline, which may not be what you want. For example, false
| true will be considered to have succeeded. If you would like this to
fail, then you can use set -o pipefail to make it fail.
Program defensively - expect the unexpected
Your script should take into account of the unexpected, like files missing
or directories not being created. There are several things you can do to prevent
errors in these situations. For example, when you create a directory, if the
parent directory doesn't exist, mkdir will return an error. If you add
a -p option then mkdir will create all the parent directories
before creating the requested directory. Another example is rm. If you
ask rm to delete a non-existent file, it will complain and your script will
terminate. (You are using -e, right?) You can fix this by using -f,
which will silently continue if the file didn't exist.
Be prepared for spaces in filenames
Someone will always use spaces in filenames or command line arguments and
you should keep this in mind when writing shell scripts. In particular you should
use quotes around variables.
if [ $filename = "foo" ];
will fail if $filename contains a space. This can be fixed by using:
if [ "$filename" = "foo" ];
When using $@ variable, you should always quote it or any arguments containing
a space will be expanded in to separate words.
david% foo() { for i in $@; do echo $i; done }; foo bar "baz quux"
bar
baz
quux
david% foo() { for i in "$@"; do echo $i; done }; foo bar "baz quux"
bar
baz quux
I can not think of a single place where you shouldn't use "$@" over $@, so
when in doubt, use quotes.
If you use find and xargs together, you should use -print0
to separate filenames with a null character rather than new lines. You then
need to use -0 with xargs.
david% touch "foo bar"
david% find | xargs ls
ls: ./foo: No such file or directory
ls: bar: No such file or directory
david% find -print0 | xargs -0 ls
./foo bar
Setting traps
Often you write scripts which fail and leave the filesystem in an inconsistent
state; things like lock files, temporary files or you've updated one file and
there is an error updating the next file. It would be nice if you could fix
these problems, either by deleting the lock files or by rolling back to a known
good state when your script suffers a problem. Fortunately bash provides a way
to run a command or function when it receives a unix signal using the trap
command.
trap command signal [signal ...]
There are many signals you can trap (you can get a list of them by running
kill -l), but for cleaning up after problems there are only 3 we are
interested in: INT, TERM and EXIT. You can also reset
traps back to their default by using - as the command.
Signal
Description
INT
Interrupt - This signal is sent when someone kills the script by
pressing ctrl-c.
TERM
Terminate - this signal is sent when someone sends the TERM signal
using the kill command.
EXIT
Exit - this is a pseudo-signal and is triggered when your script
exits, either through reaching the end of the script, an exit command
or by a command failing when using set -e.
Usually, when you write something using a lock file you would use something
like:
if [ ! -e $lockfile ]; then
touch $lockfile
critical-section
rm $lockfile
else
echo "critical-section is already running"
fi
What happens if someone kills your script while critical-section
is running? The lockfile will be left there and your script won't run again
until it's been deleted. The fix is to use:
if [ ! -e $lockfile ]; then
trap "rm -f $lockfile; exit" INT TERM EXIT
touch $lockfile
critical-section
rm $lockfile
trap - INT TERM EXIT
else
echo "critical-section is already running"
fi
Now when you kill the script it will delete the lock file too. Notice that
we explicitly exit from the script at the end of trap command, otherwise the
script will resume from the point that the signal was received.
Race conditions
It's worth pointing out that there is a slight race condition in the above
lock example between the time we test for the lockfile and the time we create
it. A possible solution to this is to use IO redirection and bash's noclobber
mode, which won't redirect to an existing file. We can use something similar
to:
if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null;
then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
critical-section
rm -f "$lockfile"
trap - INT TERM EXIT
else
echo "Failed to acquire lockfile: $lockfile."
echo "Held by $(cat $lockfile)"
fi
A slightly more complicated problem is where you need to update a bunch of
files and need the script to fail gracefully if there is a problem in the middle
of the update. You want to be certain that something either happened correctly
or that it appears as though it didn't happen at all.Say you had a script to
add users.
add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R
There could be problems if you ran out of diskspace or someone killed the
process. In this case you'd want the user to not exist and all their files to
be removed.
rollback() {
del_from_passwd $user
if [ -e /home/$user ]; then
rm -rf /home/$user
fi
exit
}
trap rollback INT TERM EXIT
add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R
trap - INT TERM EXIT
We needed to remove the trap at the end or the rollback function would
have been called as we exited, undoing all the script's hard work.
Be atomic
Sometimes you need to update a bunch of files in a directory at once, say
you need to rewrite urls form one host to another on your website. You might
write:
for file in $(find /var/www -type f -name "*.html"); do
perl -pi -e 's/www.example.net/www.example.com/' $file
done
Now if there is a problem with the script you could have half the site referring
to www.example.com and the rest referring to www.example.net. You could fix
this using a backup and a trap, but you also have the problem that the site
will be inconsistent during the upgrade too.
The solution to this is to make the changes an (almost) atomic operation.
To do this make a copy of the data, make the changes in the copy, move the original
out of the way and then move the copy back into place. You need to make sure
that both the old and the new directories are moved to locations that are on
the same partition so you can take advantage of the property of most unix filesystems
that moving directories is very fast, as they only have to update the inode
for that directory.
cp -a /var/www /var/www-tmp
for file in $(find /var/www-tmp -type f -name "*.html"); do
perl -pi -e 's/www.example.net/www.example.com/' $file
done
mv /var/www /var/www-old
mv /var/www-tmp /var/www
This means that if there is a problem with the update, the live system is
not affected. Also the time where it is affected is reduced to the time between
the two mvs, which should be very minimal, as the filesystem just has
to change two entries in the inodes rather than copying all the data around.
The disadvantage of this technique is that you need to use twice as much
disk space and that any process that keeps files open for a long time will still
have the old files open and not the new ones, so you would have to restart those
processes if this is the case. In our example this isn't a problem as apache
opens the files every request. You can check for files with files open by using
lsof. An advantage is that you now have a backup before you made your
changes in case you need to revert.
Lastpipe shell option that fix longstanding bash stupidity (bug that became
a feature) was long overdue. Negative indexes semantic borrowed from
Perl is also nice.
m. The printf builtin has a new %(fmt)T specifier, which allows time values
to use strftime-like formatting.
n. There is a new `compat41' shell option.
o. The cd builtin has a new Posix-mandated `-e' option.
p. Negative subscripts to indexed arrays, previously errors, now are treated
as offsets from the maximum assigned index + 1.
q. Negative length specifications in the ${var:offset:length} expansion,
previously errors, are now treated as offsets from the end of the variable.
... ... ...
t. There is a new `lastpipe' shell option that runs the last command of a
pipeline in the current shell context. The lastpipe option has no
effect if job control is enabled.
-------------------------------------------------------------------------------
This is a terse description of the new features added to bash-4.1 since
the release of bash-4.0. As always, the manual page (doc/bash.1) is
the place to look for complete descriptions.
e. `printf -v' can now assign values to array indices.
f. New `complete -E' and `compopt -E' options that work on the "empty"
completion: completion attempted on an empty command line.
g. New complete/compgen/compopt -D option to define a `default' completion:
a completion to be invoked on command for which no completion has been
defined. If this function returns 124, programmable completion is
attempted again, allowing a user to dynamically build a set of completions
as completion is attempted by having the default completion function
install individual completion functions each time it is invoked.
h. When displaying associative arrays, subscripts are now quoted.
i. Changes to dabbrev-expand to make it more `emacs-like': no space appended
after matches, completions are not sorted, and most recent history entries
are presented first.
j. The [[ and (( commands are now subject to the setting of `set -e' and the
ERR trap.
... ... ...
q. The < and > operators to the [[ conditional command now do string
comparison according to the current locale if the compatibility level
is greater than 40.
This is a terse description of the new features added to bash-4.2 since
the release of bash-4.1. As always, the manual page (doc/bash.1) is
the place to look for complete descriptions.
1. New Features in Bash
m. The printf builtin has a new %(fmt)T specifier, which allows time values
to use strftime-like formatting.
n. There is a new `compat41' shell option.
o. The cd builtin has a new Posix-mandated `-e' option.
p. Negative subscripts to indexed arrays, previously errors, now are treated
as offsets from the maximum assigned index + 1.
q. Negative length specifications in the ${var:offset:length} expansion,
previously errors, are now treated as offsets from the end of the variable.
... ... ...
t. There is a new `lastpipe' shell option that runs the last command of a
pipeline in the current shell context. The lastpipe option has no
effect if job control is enabled.
u. History expansion no longer expands the `$!' variable expansion.
v. Posix mode shells no longer exit if a variable assignment error occurs
with an assignment preceding a command that is not a special builtin.
w. Non-interactive mode shells exit if -u is enabled and an attempt is made
to use an unset variable with the % or # expansions, the `//', `^', or
`,' expansions, or the parameter length expansion.
x. Posix-mode shells use the argument passed to `.' as-is if a $PATH search
fails, effectively searching the current directory. Posix-2008 change.
This is from bash 41, but still important to know:
q. The < and > operators to the [[ conditional command now do string
comparison according to the current locale if the compatibility level
is greater than 40.
Bash 4 introduces the concepts of coprocesses, a well known feature in
other shells. The basic concept is simple: It will start any command in the
background and set up an array that is populated with accessible files that
represent the filedescriptors of the started process.
In other words: It lets you start a process in
background and communicate with its input and output data streams.
The mapfile builtin is able to map the lines of a file directly
into an array. This avoids to fill an array yourself using a loop. It allows
to define the range of lines to read and optionally calling a callback, for
example to display a progress bar.
The
-p option now prints all attributes and values of declared
variables (or functions, when used with -f). The output is fully
re-usable as input.
The new option -l declares a variable in a way that the content
ist converted to lowercase on assignment. Same, but for uppercase, applies to -u. The option
-c causes the content to be capitalized
before assignment.
declare -A declares associative arrays (see below).
The
read builtin command got some interesting new features.
The -t option to specify a timeout value has been slightly tuned.
It now accepts fractional values and the special value 0 (zero). When -t 0 is specified,
read immediately returns with an exit
status indicating if there's data waiting or not. However, when a timeout is
given and the read builtin times out, any partial data recieved
up to the timeout is stored in the given variable, rather than lost. When a
timeout is hit, read exits with a code greater than 128.
A new option, -i, was introduced to be able to preload the input
buffer with some text (when Readline is used, with -e). The user
is able to change the text or just press return to accept it.
Beside the use of the 512 bytes blocksize everywhere in
POSIX mode,
ulimit supports two new limits: -b
for max. socket buffer size and -T for max. number of threads.
When using substring expansion on the positional parameters, a starting index
of 0 now causes $0 to be prefixed to the list (if the positional parameters
are used at all). Before, this expansion started with $1:
# this should display $0 on Bash v4, $1 on Bash v3
echo ${@:0:1}
There's a new shell option globstar. When enabled, Bash will perform recursive globbing on
** – this means it matches all directories and files from the current
position in the filesystem, rather that only the current level.
The new shell option dirspell enables spelling corrections on directory names during globbing.
There is a new &>> redirection operator, which appends the standard
output and standard error to the named file. This is the same as the good old
>>FILE 2>&1 notation.
The parser now understands |&2>&1 |, which redirects
the standard error for a command through a pipe.
If a command is not found, the shell attempts to execute a shell function
named command_not_found_handle, supplying the command words
as the function arguments. This can be used to display userfriendly messages
or perform different command searches.
The behaviour of the set -e (errexit) mode
was changed, it now acts more intuitive (and is better documented in the
manpage).
The output target for the xtrace (set -x/set
+x) feature ist configurable since Bash 4.1 (before
it's fixed to stderr): a variable named BASH_XTRACEFD
can be set to the filedescriptor that should get the output
Look more like bash 3.3 then bash 4.0. complete absence of interesting features
and very poor understanding of the necessary path of shell development... (just
look at
William Park's
BASFDIFF and other patches and you understand this negative comment better).
The `read' builtin has a new -i option which
inserts text into the reply buffer when using readline.
The `declare' builtin now has new -l (convert
value to lowercase upon assignment) and -u (convert value to uppercase upon
assignment) options. There is an optionally-configurable -c option
to capitalize a value at assignment.
The -p option to `declare' now displays all
variable values and attributes
(or function values and attributes if used with -f).
There is a new `coproc' reserved word that specifies
a coprocess: an asynchronous command run with two pipes connected to the creating
shell. Coprocs can be named. The input and output file
descriptors and the PID of the coprocess are available to the calling shell
in variables with coproc-specific names.
There is a new `autocd' option that, when enabled, causes bash to attempt
to `cd' to a directory name that is supplied as the first word of a simple command.
[you can now type ls /bin/
and get equvalent of ls /bin; cd /bin. Is not
this stupid ? NNB]
There is a new `checkjobs' option that causes the shell to check for and
report any running or stopped jobs at exit.
The programmable completion code exports a new COMP_TYPE variable,
set to a character describing the type of completion being attempted.
The shell now has the notion of a `compatibility level', controlled by
new variables settable by `shopt'. Setting this variable
currently
restores the bash-3.1 behavior when processing quoted strings
on the rhs
of the `=~' operator to the `[[' command.
There is a new shell option: `globstar'. When enabled, the globbing code treats `**' specially
-- it matches all directories (and files within them, when appropriate)
recursively.
There is a new shell option: `dirspell'.
When enabled, the filename completion code performs spelling correction
on directory names during completion.
There is a new bash-specific bindable readline function: `dabbrev-expand'.
It uses menu completion on a set of words taken from the history list.
The parser now understands `|&' as a synonym for `2>&1 |', which redirects
the standard error for a command through a pipe.
The new `;&' case statement action list terminator causes execution to
continue with the action associated with the next pattern in the statement
rather than terminating the command.
The new `;;&' case statement action list terminator causes the shell to
test the next set of patterns after completing execution of the current
action, rather than terminating the command.
There are new case-modifying word expansions: uppercase (^[^]) and
lowercase (,[,]). They can work on either the first character or
array element, or globally. They accept an optional shell pattern
that determines which characters to modify. There is an optionally-configured
feature to include capitalization operators.
The shell provides associative array variables, with the appropriate support
to create, delete, assign values to, and expand them.
CDPATH and GLOBIGNORE are ignored when the shell is running in privileged
mode.
New Features in Readline
a. A new variable, rl_sort_completion_matches; allows applications
to inhibit match list sorting (but beware: some things don't work
right if applications do this).
b. A new variable, rl_completion_invoking_key; allows applications
to discover the key that invoked rl_complete or rl_menu_complete.
c. The functions rl_block_sigint and rl_release_sigint are now public
and available to calling applications who want to protect critical
sections (like redisplay).
d. The functions rl_save_state and rl_restore_state are now public
and available to calling applications; documented rest of readline's
state flag values.
e. A new user-settable variable, `history-size', allows setting the
maximum number of entries in the history list.
f. There is a new implementation of menu completion, with several
improvements over the old; the most notable improvement is a better
`completions browsing' mode.
g. The menu completion code now uses the rl_menu_completion_entry_function
variable, allowing applications to provide their own menu completion generators.
h. There is support for replacing a prefix of a pathname with
a `...' when displaying possible completions. This is controllable
by setting the `completion-prefix-display-length' variable.
Matches with a common prefix longer than this value have the common prefix
replaced with `...'.
i. There is a new `revert-all-at-newline' variable. If enabled,
readline will undo all outstanding changes to all history lines when `accept-line'
is executed.
j. If the kernel supports it, readline displays special characters
corresponding to a keyboard-generated signal when the signal is received.
In addition to the fairly common forms of
input/output redirection
the shell recognizes something called process substitution. Although
not documented as a form of input/output redirection, its syntax and its effects
are similar.
The syntax for process substitution is:
<(list)
or
>(list)
where each list is a command or a pipeline of commands. The effect
of process substitution is to make each list act like a file. This is done by
giving the list a name in the file system and then substituting that
name in the command line. The list is given a name either by connecting the
list to named pipe or by using a file in /dev/fd (if supported by the
O/S). By doing this, the command simply sees a file name and is unaware that
its reading from or writing to a command pipeline.
To substitute a command pipeline for an input file the syntax is:
command ... <(list) ...
To substitute a command pipeline for an output file the syntax is:
command ... >(list) ...
At first process substitution may seem rather pointless, for example you
might imagine something simple like:
uniq <(sort a)
to sort a file and then find the unique lines in it, but this is more commonly
(and more conveniently) written as:
sort a | uniq
The power of process substitution comes when you have multiple command pipelines
that you want to connect to a single command.
For example, given the two files:
# cat a
e
d
c
b
a
# cat b
g
f
e
d
c
b
To view the lines unique to each of these two unsorted files you might do something
like this:
# sort a | uniq >tmp1
# sort b | uniq >tmp2
# comm -3 tmp1 tmp2
a
f
g
# rm tmp1 tmp2
With process substitution we can do all this with one line:
# comm -3 <(sort a | uniq) <(sort b | uniq)
a
f
g
Depending on your shell settings you may get an error message similar to:
syntax error near unexpected token `('
when you try to use process substitution, particularly if you try to use it
within a shell script. Process substitution is not a POSIX compliant feature
and so it may have to be enabled via:
set +o posix
Be careful not to try something like:
if [[ $use_process_substitution -eq 1 ]]; then
set +o posix
comm -3 <(sort a | uniq) <(sort b | uniq)
fi
The command set +o posix enables not only the execution of process
substitution but the recognition of the syntax. So, in the example above the
shell tries to parse the process substitution syntax before the "set" command
is executed and therefore still sees the process substitution syntax as illegal.
Of course, note that all shells may not support process substitution, these
examples will work with bash.
This version is specially book-formatted for duplex printing and is usually
more up-to-he version you can download from the LDP site. Note that it's a 2.6
MB download.
Ease your system administration tasks by taking advantage of key parts of
the Bourne-again shell (bash) and its features. Bash is a popular alternative
to the original Bourne and Korn shells. It provides an impressive range of additional
functionality that includes improvements to the scripting environment, extensive
aliasing techniques, and improved methods for automatically completing different
commands, files, and paths.
Do you sometimes wonder how to use parameters with your scripts, and how
to pass them to internal functions or other scripts? Do you need to do simple
validity tests on parameters or options, or perform simple extraction and replacement
operations on the parameter strings? This tip helps you with parameter use and
the various parameter expansions available in the bash shell.
About: The Advanced Bash Scripting Guide is both a reference and a
tutorial on shell scripting. This comprehensive book (the equivalent of 880+
print pages) covers almost every aspect of shell scripting. It contains 340
profusely commented illustrative examples, a number of tables, and a cross-linked
index/glossary. Not just a shell scripting tutorial, this book also provides
an introduction to basic programming techniques, such as sorting and recursion.
It is well suited for either individual study or classroom use. It covers Bash,
up to and including version 3.2x.
Changes: Many bugfixes and stylistic cleanups were done. Four new
example scripts were added. A new subsection on version 3.2 Bash update was
added. Explanations of certain difficult concepts were clarified. This is an
important update.
Bash-3.2 is the second maintenance release of the third major release of
bash. It contains the following significant new features (see the manual
page for complete descriptions and the CHANGES and NEWS files in the bash-3.2
distribution).
Bash-3.2 now checks shell scripts for NUL characters rather than non-printing
characters when deciding whether or not a script is a binary file.
Quoting the string argument to the [[ command's =~ (regexp) operator
now forces string matching, as with the other pattern-matching operators.
A short feature history dating from Bash-2.0:
Bash-3.1 contained the following new features:
Bash-3.1 may now be configured and built in a mode that enforces strict
POSIX compliance.
The `+=' assignment operator, which appends to
the value of a string or array variable, has been implemented.
It is now possible to ignore case when matching in contexts other than
filename generation using the new `nocasematch' shell option.
Bash-3.0 contained the following new features:
Features to support the bash debugger have been implemented, and there
is a new `extdebug' option to turn the non-default options on
HISTCONTROL is now a colon-separated list of options
and has been extended with a new `erasedups' option that will result
in only one copy of a command being kept in the history list
Brace expansion has been extended with a new {x..y} form, producing
sequences of digits or characters
Timestamps are now kept with history entries, with an option to save
and restore them from the history file; there is a new HISTTIMEFORMAT
variable describing how to display the timestamps when listing history
entries
The `[[' command can now perform extended regular expression (egrep-like)
matching, with matched subexpressions placed in the BASH_REMATCH array
variable
A new `pipefail' option causes a pipeline to return a failure status if
any command in it fails
The `jobs', `kill', and `wait' builtins now accept job control notation
in their arguments even if job control is not enabled
The `gettext' package and libintl have been integrated, and the shell
messages may be translated into other languages
Bash-2.05b introduced the following new features:
support for multibyte characters has been added to both bash and readline
the DEBUG trap is now run *before* simple commands, ((...)) commands,
[[...]] conditional commands, and for ((...)) loops
the shell now performs arithmetic in the largest integer size the machine
supports (intmax_t)
there is a new \D{...} prompt expansion; passes the `...' to strftime(3)
and inserts the result into the expanded prompt
there is a new `here-string' redirection operator:
<<< word
when displaying variables, function attributes and definitions are shown
separately, allowing them to be re-used as input (attempting to re-use
the old output would result in syntax errors).
o `read' has a new `-u fd' option to read from a specified file descriptor
the bash debugger in examples/bashdb has been modified to work with the
new DEBUG trap semantics, the command set has been made more gdb-like,
and the changes to $LINENO make debugging functions work better
the expansion of $LINENO inside a shell function is only relative to the
function start if the shell is interactive -- if the shell is running a
script, $LINENO expands to the line number in the script. This is as
POSIX-2001 requires
Bash-2.05a introduced the following new features:
The `printf' builtin has undergone major work
There is a new read-only `shopt' option: login_shell, which is set by
login shells and unset otherwise
New `\A' prompt string escape sequence; expanding to time in 24-hour
HH:MM format
New `-A group/-g' option to complete and compgen; goes group name
completion
New [+-]O invocation option to set and unset `shopt' options at startup
ksh-like `ERR' trap
o `for' loops now allow empty word lists after the `in' reserved word
new `hard' and `soft' arguments for the `ulimit' builtin
Readline can be configured to place the user at the same point on the line
when retrieving commands from the history list
Readline can be configured to skip `hidden' files (filenames with a leading
`.' on Unix) when performing completion
Bash-2.05 introduced the following new features:
This version has once again reverted to using locales and strcoll(3) when
processing pattern matching bracket expressions, as POSIX requires.
Added a new `--init-file' invocation argument as a synonym for `--rcfile',
per the new GNU coding standards.
The /dev/tcp and /dev/udp redirections now accept service names as well
as port numbers.<li>`complete' and `compgen' now take a `-o value'
option, which controls some of the aspects of that compspec.
Valid values are:
default - perform bash default
completion if programmable
completion produces no matches
dirnames - perform directory name completion if programmable
completion produces no matches
filenames - tell readline that the compspec produces filenames,
so it can do things like append slashes to
directory names and suppress trailing spaces
A new loadable builtin, realpath, which canonicalizes and expands symlinks
in pathname arguments.
When `set' is called without options, it prints function defintions in a
way that allows them to be reused as input. This affects `declare' and
`declare -p' as well. This only happens when the shell is not in POSIX
mode, since POSIX.2 forbids this behavior.
Bash-2.04 introduced the following new features:
Programmable word completion with the new `complete' and `compgen' builtins;
examples are provided in examples/complete/complete-examples
`history' has a new `-d' option to delete a history
entry
`bind' has a new `-x' option to bind key sequences
to shell commands
The prompt expansion code has new `\j' and `\l' escape sequences
The `no_empty_cmd_completion' shell option, if enabled, inhibits
command completion when TAB is typed on an empty line
`help' has a new `-s' option to print a usage synopsis
New arithmetic operators: var++, var--, ++var, --var, expr1,expr2 (comma)
New ksh93-style arithmetic for command:
for ((expr1 ; expr2; expr3 )); do list; done<li>`read' has new options: `-t',
`-n', `-d', `-s'
The redirection code handles several filenames specially: /dev/fd/N,
/dev/stdin, /dev/stdout, /dev/stderr
The redirection code now recognizes /dev/tcp/HOST/PORT and /dev/udp/HOST/PORT
and tries to open a TCP or UDP socket, respectively, to the specified
port on the specified host
The ${!prefix*} expansion has been implemented
A new FUNCNAME variable, which expands to the name of a currently-executing
function
The GROUPS variable is no longer readonly
A new shopt `xpg_echo' variable, to control the behavior of echo with
respect to backslash-escape sequences at runtime
The NON_INTERACTIVE_LOGIN_SHELLS #define has returned
The version of Readline released with Bash-2.04, Readline-4.1, had several
new features as well:
Parentheses matching is always compiled into readline, and controllable
with the new `blink-matching-paren' variable
The history-search-forward and history-search-backward functions now leave
point at the end of the line when the search string is empty, like
reverse-search-history, and forward-search-history
A new function for applications: rl_on_new_line_with_prompt()
New variables for applications: rl_already_prompted, and rl_gnu_readline_p
Bash-2.03 had very few new features, in keeping with the convention that
odd-numbered releases provide mainly bug fixes. A number of new features
were added to Readline, mostly at the request of the Cygnus folks.
A new shopt option, `restricted_shell', so that startup files can test
whether or not the shell was started in restricted mode Filename generation
is now performed on the words between ( and ) in
compound array assignments (this is really a bug fix) OLDPWD is now auto-exported,
as POSIX.2 requires ENV and BASH_ENV are read-only variables in a restricted
shell Bash may now be linked against an already-installed Readline library,
as long as the Readline library is version 4 or newer All shells begun with
the `--login' option will source the login shell
startup files, even if the shell is not interactive
There were lots of changes to the version of the Readline library released
along with Bash-2.03. For a complete list of the changes, read the file
CHANGES in the Bash-2.03 distribution.
Bash-2.02 contained the following new features:
a new version of malloc (based on the old GNU malloc code in previous
bash versions) that is more page-oriented, more conservative
with memory usage, does not `orphan' large blocks when they
are freed, is usable on 64-bit machines, and has allocation
checking turned on unconditionally POSIX.2-style globbing character classes
([:alpha:], [:alnum:], etc.) POSIX.2-style globbing equivalence classes POSIX.2-style
globbing collating symbols the ksh [[...]] extended conditional command the
ksh egrep-style extended pattern matching operators a new `printf' builtin the
ksh-like $(<filename) command substitution, which is equivalent to
$(cat filename) new tilde prefixes that expand to directories from the directory
stack new `**' arithmetic operator to do exponentiation case-insensitive globbing
(filename expansion) menu completion a la tcsh `magic-space' history expansion
function like tcsh the readline inputrc `language' has a new file inclusion
directive ($include)
Bash-2.01 contained only a few new features:
new `GROUPS' builtin array variable containing the user's group list new
bindable readline commands: history-and-alias-expand-line and
alias-expand-line
Bash-2.0 contained extensive changes and new features from bash-1.14.7. Here's
a short list:
new `time' reserved word to time pipelines, shell builtins, and
shell functions one-dimensional arrays with a new compound assignment statement,
appropriate expansion constructs and modifications to some
of the builtins (read, declare, etc.) to use them new quoting syntaxes for ANSI-C
string expansion and locale-specific
string translation new expansions to do substring extraction, pattern replacement,
and indirect variable expansion
new builtins: `disown' and `shopt' new variables: HISTIGNORE, SHELLOPTS, PIPESTATUS,
DIRSTACK, GLOBIGNORE,
MACHTYPE, BASH_VERSINFO special handling of many unused or redundant variables
removed (e.g., $notify, $glob_dot_filenames,
$no_exit_on_failed_exec) dynamic loading of new builtin commands; many loadable
examples provided new prompt expansions: \a, \e, \n, \H, \T, \@, \v, \V history
and aliases available in shell scripts new readline variables: enable-keypad,
mark-directories, input-meta,
visible-stats, disable-completion, comment-begin new readline commands to manipulate
the mark and operate on the region new readline emacs mode commands and bindings
for ksh-88 compatibility updated and extended builtins new DEBUG trap expanded
(and now documented) restricted shell mode
implementation stuff: autoconf-based configuration nearly all
of the bugs reported since version 1.14 have been fixed most builtins converted
to use builtin `getopt' for consistency most builtins use -p option to display
output in a reusable form (for
consistency) grammar tighter and smaller (66 reduce-reduce conflicts gone) lots
of code now smaller and faster test suite greatly expanded
B2) Are there any user-visible incompatibilities between bash-3.2 and
bash-2.05b?
There are a few incompatibilities between version 2.05b and version 3.2.
They are detailed in the file COMPAT in the bash distribution. That file
is not meant to be all-encompassing; send mail to bash-maintain...@gnu.org
if if you find something that's not mentioned there.
Set this to to avoid having consecutive duplicate commands and other
not so useful information appended to the history list. This will cut down
on hitting the up arrow endlessly to get to the command before the one you
just entered twenty times. It will also avoid filling a large percentage
of your history list with useless commands.
Try this:
$ export HISTIGNORE="&:ls:ls *:mutt:[bf]g:exit"
Using this, consecutive duplicate commands, invocations of ls,
executions of the mutt
mail client without any additional parameters, plus calls to the bg,
fg and exit built-ins will not be appended to the history
list.
readline Tips and Tricks
The readline library is used by bash and many other programs to read a line
from the terminal, allowing the user to edit the line with standard
Emacs editing keys.
set show-all-if-ambiguous on
If you have this in your /etc/inputrc or ~/.inputrc,
you will no longer have to hit the <Tab> key twice to produce
a list of all possible completions. A single <Tab> will suffice.
This setting is highly recommended.
set visible-stats on
Adding this to your /etc/inputrc or ~/.inputrc will
result in a character being appended to any file-names returned by completion,
in much the same way as ls -F works.
If you're a fan of vi as opposed to Emacs, you might prefer to operate
bash in vi editing mode. Being a GNU program, bash uses Emacs bindings unless
you specify otherwise.
Set the following in your /etc/inputrc or ~/.inputrc:
set editing-mode viset keymap vi
and this in your /etc/bashrc or ~/.bashrc:
set -o vi
Some people prefer the non-incremental style of history completion,
as opposed to the incremental style offered by C-r and C-s.
This is the style of history completion offered by csh.
bash offers bindings for this, but they are unbound by default.
Set the following in your /etc/inputrc or ~/.inputrc:
Do you sometimes wonder how to use parameters with your scripts, and how
to pass them to internal functions or other scripts? Do you need to do simple
validity tests on parameters or options, or perform simple extraction and replacement
operations on the parameter strings? This tip helps you with parameter use and
the various parameter expansions available in the bash shell.
BashDiff
is a patch against
Bash-3.0
shell, incorporating many useful features from Awk, Python, Zsh, Ksh, and others.
It implements in the main core
new brace expansion {a..b} --- integer/letter generation, positional
parameters and array expansion
new parameter expansion ${var|...} --- content filtering, list comprehension
(like Python), regex/string splitting and joining, Python-like string methods,
emulation of associative array lookup, etc.
new command substitution $(=...) --- floating-point hook to Awk
extended case statement --- regex, continuation, then/else sections
GDBM, SQLite, PostgreSQL, and MySQL database interface
Expat XML parser interface
stack/queue operations on arrays and positional parameters
x-y character plot
Libwebserver (embedded web server) interface
GTK+2 interface for simple GUI dialog or layout
Release focus:
Major feature enhancements
Changes:
This release adds a shell interface to GTK+2 widget library, for building a
simple GUI dialog or layout. It uses XML syntax for layout, and returns the
user's selection in a shell variable or runs a shell command as callback. The
name of the 'xml' builtin has been changed to 'expat'. The <<+ here document
now removes space and tab indents.
More bugfixes were made, some of them fairly important. New material was added,
including a few rather useful example scripts. This is more than a "minor" update,
but not quite a major one.
The Advanced Bash Scripting Guide is both a reference and a tutorial on shell
scripting. This comprehensive book (the equivalent of about 646 print pages)
covers almost every aspect of shell scripting. It contains over 300 profusely
commented illustrative examples, and a number of tables. Not just a shell scripting
tutorial, this book also provides an introduction to basic programming techniques,
such as sorting and recursion. It is well suited for either individual study
or classroom use.
For Login shells (subject
to the -noprofile option):
On logging in:
If `/etc/profile' exists, then source it.
If `~/.bash_profile' exists, then source it,
else if `~/.bash_login' exists, then source it,
else if `~/.profile' exists, then source it.
On logging out:
If `~/.bash_logout' exists, source it.
For non-login interactive shells (subject to the -norc and -rcfile options):
On starting up:
If `~/.bashrc' exists, then source it.
For non-interactive shells:
On starting up:
If the environment variable `ENV' is non-null, expand the variable and source
the file named by the value. If Bash is not started in Posix mode, it looks
for `BASH_ENV' before `ENV'.
So, typically, your `~/.bash_profile' contains the line
`if [ -f `~/.bashrc' ]; then source `~/.bashrc'; fi' after (or before) any login
specific initializations.
If Bash is invoked as `sh', it tries to mimic the behavior of `sh' as closely
as possible. For a login shell, it attempts to source only `/etc/profile' and
`~/.profile', in that order. The `-noprofile' option may still be used to disable
this behavior. A shell invoked as `sh' does not attempt to source any other
startup files.
When Bash is started in POSIX mode, as with the `-posix' command line option,
it follows the Posix 1003.2 standard for startup files. In this mode, the `ENV'
variable is expanded and that file sourced; no other startup files are read.
Although the Readline library comes with a set of Emacs-like key bindings
installed by default, it is possible that you would like to use a different
set of keybindings. You can customize programs that use Readline by putting
commands in an "init" file in your home directory. The name of this file is
taken from the value of the shell variable `INPUTRC'. If that variable is unset,
the default is `~/.inputrc'.
When a program which uses the Readline library starts up, the init file is
read, and the key bindings are set.
In addition, the `C-x C-r' command re-reads this init file, thus incorporating
any changes that you might have made to it.
Conditional Init Constructs within readline
Readline implements a facility similar in spirit to the
conditional compilation features of the C preprocessor which allows key bindings
and variable settings to be performed as the result of tests. There are three
parser directives used.
`$if'
The `$if' construct allows
bindings to be made based on the editing mode, the terminal being used,
or the application using Readline. The text of the test extends to the
end of the line; no characters are required to isolate it.
`mode'
The `mode=' form of the
`$if' directive is used to test whether Readline is in `emacs' or `vi'
mode. This may be used in conjunction with the `set keymap' command,
for instance, to set bindings in the `emacs-standard' and `emacs-ctlx'
keymaps only if Readline is starting out in `emacs' mode.
`term'
The `term=' form may be
used to include terminal-specific key bindings, perhaps to bind the
key sequences output by the terminal's function keys. The word on the
right side of the `=' is tested against the full name of the terminal
and the portion of the terminal name before the first `-'. This allows
SUN to match both SUN and SUN-CMD, for instance.
`application'
The APPLICATION construct
is used to include application-specific settings. Each program using
the Readline library sets the APPLICATION NAME, and you can test for
it. This could be used to bind key sequences to
functions useful for a specific program.
`$endif'
This command terminates
an `$if' command.
`$else'
Commands in this branch
of the `$if' directive are executed if the test fails.
The following command adds a key sequence
that quotes the current or previous word in Bash:
$if bash
# Quote the current or previous word
"\C-xq": "\eb\"\ef\""
$endif
kcd is a directory change utility under Linux or any other Unix clones. It
helps you navigate the directory tree. You can supply the desired directory
name in the command line and let kcd find it for you or let kcd show the entire
directory tree and use arrow keys to go to the destination directory.
Here is a list some features available in kcd:
Fast directory rescanning. All directory timestamp is saved so that
certain directories do not need rescanning if they are not changed.
When supply directory in command line and kcd find too many matches.
kcd shows all of them and let you select using cursor keys.
You can tell kcd to skip some directory. You can also chose whether
you want the whole directory tree, inside your home directory, etc. They
can be set in kcd configuration file.
Supports bash, ash, pdksh, zsh and tcsh.
Multiple configuration profiles.
Priority directory matching via bookmark.
Fuzzy directory searching (Contributed by Robert Sandilands).
Supports UTF-8 Unicode encoding with combining characters.
Supports localization.
Default, vi, and emacs key binding modes.
Partial directory tree display.
Display directory tree without saved data.
kcd is available as stable version and development version. You can distinguish
development version from stable version by looking at its version number. Beginning
from version 5.0.0, any version x.y.z where y is even is a stable version. Those
where y is odd is a development version. Features currently present in the development
version will eventually appear in the future stable version 8.0.0.
Another Norton Change Directory (NCD) clone with more features.
Wcd is a program to change directory fast. It saves time typing at the keyboard.
One needs to type only a part of a directory name and wcd will jump to it. By
default wcd searches for a directory with a name that begins with what has been
typed, but the use of wildcards is also fully supported.
For instance:
wcd Desk
will change to directory /home/waterlan/Desktop
But also
wcd *top
will do that.
Wcd is free to use and you can get the source code too.
Some features of wcd:
Full screen interactive directory browser with speed search.
Present the user a list in case of multiple matches.
Wildcards *, ? and [SET] supported.
Directory stack, push pop.
Subdir definition possible. E.g. wcd subdira/subdirb
Long directory names support in Win95/98/NT DOS-box
Windows LAN UNC paths supported.
Change drive and directory at once.
Alias directories.
Ban directories.
'cd' behaviour
Free portable source-code, no special libraries required
Multi platform:
DOS 16 bit, DOS 32 bit, DOS bash, Windows 3.1/95/NT DOS-box, Cygwin bash,
Unix ksh, csh, bash and zsh.
Wcd has been tested on:
FreeDOS, MS-DOS 6.2, Win95, Win98, Windows NT 4.0,
Linux,
FreeBSD, HP-UX, SunOS,
Solaris, SGI IRIX. Wcd works on any PC and can be ported to any Unix system.
WCD is free software, distributed under GNU General Public License.
bash-3.0-sol9-sparc-local.gz Bash is an sh-compatible shell that incorporates
useful features from the Korn shell (ksh) and C shell (csh) - installs in
/usr/local.
Solaris 8
bash-3.0-sol8-sparc-local.gz Bash is an sh-compatible shell that incorporates
useful features from the Korn shell (ksh) and C shell (csh) - installs in
/usr/local.
This is a terse description of the new features added to bash-3.0 since the
release of bash-2.05b. As always, the manual page (doc/bash.1) is the place
to look for complete descriptions.
cc. The [[ ... ]] command has a new binary `=~' operator that performs
extended regular expression (egrep-like) matching.
l. New invocation option: --debugger. Enables debugging
and turns on new `extdebug' shell option.
f. HISTCONTROL may now include the `erasedups' option, which causes
all lines matching a line being added to be removed from the history list.
j. for, case, select, arithmetic commands now keep line number information
for the debugger.
p. `declare -F' now prints out extra line number and source file information
if the `extdebug' option is set.
r. New `caller' builtin to provide a call stack for the bash
debugger.
t. `for', `select', and `case' command heads are printed when `set
-x' is enabled.
u. There is a new {x..y} brace expansion, which is shorthand
for {x.x+1, x+2,...,y}. x and y can be integers or single characters;
the sequence may ascend or descend; the increment is always 1.
v. New ksh93-like ${!array[@]} expansion, expands to all the keys (indices)
of array.
z. New `-o plusdirs' option to complete and compgen; if set, causes
directory name completion to be performed and the results added to the
rest of the possible completions.
ee. Subexpressions matched by the =~ operator are placed in the new
BASH_REMATCH array variable.
gg. New `set -o pipefail' option that causes a pipeline to return a failure
status if any of the processes in the pipeline fail, not just the last
one.
kk. The `\W' prompt expansion now abbreviates $HOME as `~', like `\w'.
ll. The error message printed when bash cannot open a shell script supplied
as argument 1 now includes the name of the shell, to better identify the error
as coming from bash.
2. New Features in Readline
a. History expansion has a new `a' modifier equivalent to the `g' modifier
for compatibility with the BSD csh.
b. History expansion has a new `G' modifier equivalent to the BSD csh
`g'
modifier, which performs a substitution once per word.
c. All non-incremental search operations may now undo the operation
of replacing the current line with the history line.
d. The text inserted by an `a' command in vi mode can be reinserted
with `.'.
e. New bindable variable, `show-all-if-unmodified'. If set, the
readline completer will list possible completions immediately if there is more
than one completion and partial completion cannot be performed.
g. History list entries now contain timestamp information; the history
file functions know how to read and write timestamp information associated with
each entry.
n. When listing completions, directories have a `/' appended if the
`mark-directories' option has been enabled.
Doesn't seem to be much changed given the version
number increase. [[ =~ ]] can match regexes and it can do zsh style
{1..3} expansions. Improved multibyte support too. There were bigger
changes in some of the 2.0x updates.
Globs are more powerful:
**/*.c will recursively search for.c files: much quicker to type than
find.
You can match file types: e.g. *(@) will get
you symlinks. *(U) gets files owned by you.
Syntax for alternation is a lot
easier. No @(this|that) or !(*.f). Instead,
it is (this|that) and ^*.f
Next point is completion.
It includes a vast range of definitions so completion
works well for lots of commands. The completion
system handles completing parts of words so
it better handles user@host completion. You
get descriptions with completion match listings.
Completion also has a really powerful context
sensitive configuration system so you can make
it work the way you like.
It has modules. For running
a simple shell script it will actually use less
space than bash because it doesn't need to load
the line editor and other interactive related
code into memory.
There is much much more. It
takes a while to learn everything but if you
just enable the completion functions (autoload
-U compinit; compinit) you'll find it better
than bash or tcsh from day 1.
Bash
developers have different priorities.
Bash became the default primarily because it is GNU.
Zsh has some ugly but powerful features like nested expansions. The
two areas where bash is better than zsh is multibyte support and POSIX
compliance. Much of that was contributed by IBM and Apple respectively.
But if you use the shell a lot, you'll find zsh does a lot of things
better. The completion is amazing. And when it isn't emulating sh/posix,
it fixes some of the broken design decisions (like word splitting of
variables) which saves you from doing stupid things.
The FSF actually does development in a very closed
manner when it can (the gcc egcs split was partly because of this).
Bash is a good example of this. That perhaps a good thing because it
is probably good that bash doesn't get some of zsh's nasty (but powerful)
features. And if zsh didn't exist, bash might have been forked by now.
If you care about your shell, you'll find much more of a community on
the zsh lists than the spam filled bug-bash list. You can't even get
at alpha releases of bash without being one of the chosen few.
I read the announcement and it mentions "History
traversal with arrow keys", but what I would really like doesn't seem
to be mentioned (but perhaps it is possible with bash-2.05, I'm not
much of a shell expert). In Matlab, the up-arrow key searches the history
for commands that match all the characters on the line. No characters
and it acts like a normal bash arrow, if "figure, plot" is at the beginning
of the line, it will quickly scroll through all plotting commands that
have been entered at the shell.
Any idea if
this is possible?
Dara Parsavand
TThe
Fine Print: The following comments are owned by whoever
posted them. We are not responsible for them in any way.
I like bash, but the one thing that it doesn't support
(out-of-the-box anyway) is auto-completion a la W2K. In NT, when you
hit tab, you can cycle through all the words that can complete the letters
you typed... on bash, it shows you a list.
Is
there a way to make bash behave more like W2K in this sense?
The completion ability of bash has been steadily
improving. There is a nice script
here [caliban.org] that sets up a lot of good completion rules for
bash.
Until now I have sticked with tcsh for one single reason:
history substition [go.dlr.de]!
Basically it lets me insert text from my history (including
the current line) using few symbols (e.g. !$ is the last argument of
the previous line) -- it's extremely powerful, e.g. it allows to search
in the history and can do substitutions in results, or head/tail for
paths etc.
I use it a lot to keep down the number of characters
I need to type, and I have even assigned hotkeys to some of the substitutions
I use the most.
This is really the make-or-break feature for wether
or not I want to use a shell, so I really hope zsh has something similar!?!
A sane auto-completion system. That is, "cvs
<tab>" gives a list of all of the commands that cvs understands.
"cvs -<tab>" (same as above but tabbing after typing "-") gives
a list of all of the options that cvs understands. These are good
things. Now, in fairness, bash also has a command completion library.
Unfortunately, it's implemented as a huge set of Bash functions.
In zsh, "set|wc" returns 179 lines. In bash, "set|wc" returns 3,961
lines. The net effect is that zsh's system is noticeably faster
and less polluting to the local environment.
Modules. Wrappers for TCP connections, a built-in
cron thingy, and PCRE are all loadable modules to do tricky things
easily.
Lots of pre-defined things. Load the "colors"
and "zsh/terminfo" modules and you get defined associative arrays
like $fg, which emits terminal-appropriate escape codes
to set the foreground color of printed text. The command "echo
${fg[red]}red text${fg[default]}normal text" prints "red text"
in red, and "normal text" in your default color.
Bash is a good shell, and I have nothing bad to say
about it. However, zsh seems to have been designed from the ground up
by power users and for power users. I absolutely love it and everyone
that I've given a example config file to (to get them running with little
hassle) has permanently switched.
As the maintainer of
FreeBSD's bash-completion [freshports.org] port, I'm reasonably
familiar with it. Yes, it's approximately as powerful as zsh's completion
module. Still, have you ever looked at it? It's a giant set of defined
functions and glue. Seriously, get to a bash prompt and type "set" to
see all of the things that've been stuffed into your shell's namespace.
Now, try that with zsh and be pleasantly surprised.
As I said in another post, a big side effect is that
zsh's completions seem to be much faster than bash's. That alone is
worth the price of admission for me.
Believe it or not, -most- of the large companies
that use GPL'ed tools give back to the community.
Apple has done numerous fixes, not just on BASH.
Sun (disclaimer: for whom I work) has done -tons-
of work on GNOME, Mozilla and don't forget Open Office (just to name
a few).
IBM works on many projects and gives back... plus contributing all new things like JFS.
All the distro makers like Red Hat, Novell, etc give
back tons.
Each of those companies pay engineers to fix pieces
not done in Open Source projects as well as to extend them for their
customers. The patches are covered under GPL just like the main code,
and these companies know it and yet knowingly dedicate serious money
and hours to these projects. And then they satisfy the GPL by putting
them out on source CDs or submitting them back to the main projects.
The big problem for getting submitted code accepted
is that these companies are usually fixing and developing on a codebase
that is aging. For instance, Sun did numerous I18N fixes for GNOME 2.6,
but by the time they were ready the main GNOME organization had moved
on to 2.8. That means there is a disconnect between the two and the
changes have to be ported forward before they will hit the main code
branch. The same problem can happen with kernel patches and just about
any other codebase that changes versions so quickly.
Sorry, you were doing the good thing and pointing
out Apple's contributions. But so many people think these companies
violate the GPL (in spirit if not in law) when they are very large contributors
to open source. Sure, some do, and the community usually find out about
it and shame them into minimal compliance (Linksys and Sveasoft come
to mind after my delving into alternate WRT54G firmwares last night),
but generally speaking the big companies have been a good part of the
community.
Looks like a nice Unicode-savvy release that should help with dealing
with international languages at the command line. And yay to Apple for
giving back (again). When will people finally accept that Apple is indeed
helping out the OSS community through GCC, bash, and other tools...?
Kind of off-topic, but for speed purposes in scripts
that have to run fast, I find nothing better or more convenient than
Ash, especially on systems where/bin/sh is a symlink to/bin/bash.
Does anyone know any history on this shell? Is it
a clone of the original bourne shell or of bash? I can't seem to find
anything useful on Google...
BASH Debugger provides a patched BASH that enables better debugging support
as well as improved error reporting. It also contains the most comprehensive
source code debugger for BASH that has been written. It can be used as a springboard
for other experimental features (such as a timestamped history file), since
dnter"
Re: Suggestions for corrections to executable.el - use of PATHEXT
Date:
Sun, 12 Sep 2004 12:56:08 +0200
From: "Eli Zaretskii" <address@bogus.example.com>
> First, I'm not sure we should look at PATHEXT. That variable is AFAIK
> looked at by the shell, so if we want Emacs behave _exactly_ like the
> shell does, we should at least look at the value of SHELL and/or
> ComSpec (and COMSPEC for older systems). I mean, what if the user's
> shell is Bash, which AFAIK doesn't look at PATHEXT at all? And if the
> shell is COMMAND.COM, then ".cmd" should not be in the list. Etc.,
> etc.
PATHEXT is looked at by cmd.exe (the default shell on the NT hereditary
line). I do not know if it is used by command.com (the default shell on the
95 line) but I doubt it. When I tested now I found that the Run entry in
Windows Start menu honor the default extensions for PATHEXT (.com, .exe.,
.bat, .cmd). It does not however not recognize .pl which I have in my
PATHEXT (cmd.exe recognize it). I am using NT4 when testing this.
So perhaps not even ms windows is consistent here. What seems clear however
is that the main purpose of PATHEXT is as far as I can see to make it easier
for the user when entering a command interactively. The user may for example
type "notepad" instead of "notepad.exe".
PATHEXT is set by the user and expresses the users wish to type less. It
seems reasonable to use PATHEXT for this purpose in Emacs too. The variable
executable-binary-suffixes is (if I understand this correctly) used for this
purpose by executable-find. This is however not clearly expressed in the
documentation.
A note: w32-shell-execute does something quite different. It calls the ms
windows API ShellExecute to do the action associated with a certain "verb"
on a file type (on windows this means file extension). Typical verbs are
"open" and "print". Windows Explorer uses this.
Having said all this I just want to say that I regret that I took this issue
up without looking closer at the problem.
- Lennart
FAIR USE NOTICE This site contains
copyrighted material the use of which has not always been specifically
authorized by the copyright owner. We are making such material available
in our efforts to advance understanding of environmental, political,
human rights, economic, democracy, scientific, and social justice
issues, etc. We believe this constitutes a 'fair use' of any such
copyrighted material as provided for in section 107 of the US Copyright
Law. In accordance with Title 17 U.S.C. Section 107, the material on
this site is distributed without profit exclusivly for research and educational purposes. If you wish to use
copyrighted material from this site for purposes of your own that go
beyond 'fair use', you must obtain permission from the copyright owner.
ABUSE: IPs or network segments from which we detect a stream of probes might be blocked for no
less then 90 days. Multiple types of probes increase this period.
The site uses AdSense so you need to be aware of Google privacy policy. You you do not want to be
tracked by Google please disable Javascript for this site. This site is perfectly usable without
Javascript.
Original materials copyright belong
to respective owners. Quotes are made for educational purposes only
in compliance with the fair use doctrine.
FAIR USE NOTICE This site contains
copyrighted material the use of which has not always been specifically
authorized by the copyright owner. We are making such material available
to advance understanding of computer science, IT technology, economic, scientific, and social
issues. We believe this constitutes a 'fair use' of any such
copyrighted material as provided by section 107 of the US Copyright Law according to which
such material can be distributed without profit exclusively for research and educational purposes.
This is a Spartan WHYFF (We Help You For Free)
site written by people for whom English is not a native language. Grammar and spelling errors should
be expected. The site contain some broken links as it develops like a living tree...
You can use PayPal to make a contribution, supporting development
of this site and speed up access. In case softpanorama.org is down you can use the at softpanorama.info
Disclaimer:
The statements, views and opinions presented on this web page are those of the author (or
referenced source) and are
not endorsed by, nor do they necessarily reflect, the opinions of the author present and former employers, SDNP or any other organization the author may be associated with.We do not warrant the correctness
of the information provided or its fitness for any purpose.