In PHP, strings are concatenated together as follows:
Here, $foo
becomes “Hello World”.
How is this accomplished in Bash?
In general to concatenate two variables you can just write them one after another:
Bash also supports a +=
operator as shown in this code:
As this question stand specifically for Bash, my first part of the answer would present different ways of doing this properly:
+=
: Append to variable
The syntax +=
may be used in different ways:
var+=...
(Because I am frugal, I will only use two variables foo
and a
and then re-use the same in the whole answer. ;-)
Using the Stack Overflow question syntax,
works fine!
((var+=...))
variable a
is a string, but also an integer
var+=(...)
Our a
is also an array of only one element.
Note that between parentheses, there is a space separated array. If you want to store a string containing spaces in your array, you have to enclose them:
Hmm.. this is not a bug, but a feature… To prevent bash to try to develop !"
, you could:
a+=(one word "hello world"! 'hello world!' $'hello world\041')
declare -p a
declare -a a='([0]="36" [1]="18" [2]="one" [3]="word" [4]="hello world!" [5]="h
ello world!" [6]="hello world!")'
printf
: Re-construct variable using the builtin command
The printf
builtin command gives a powerful way of drawing string format. As this is a Bash builtin, there is a option for sending formatted string to a variable instead of printing on stdout
:
There are seven strings in this array. So we could build a formatted string containing exactly seven positional arguments:
printf -v a "%s./.%s...'%s' '%s', '%s'=='%s'=='%s'" "${a[@]}"
echo $a
36./.18...'one' 'word', 'hello world!'=='hello world!'=='hello world!'
Or we could use one argument format string which will be repeated as many argument submitted…
Note that our a
is still an array! Only first element is changed!
declare -p a
declare -a a='([0]="36./.18...'\''one'\'' '\''word'\'', '\''hello world!'\''=='\
''hello world!'\''=='\''hello world!'\''" [1]="18" [2]="one" [3]="word" [4]="hel
lo world!" [5]="hello world!" [6]="hello world!")'
Under bash, when you access a variable name without specifying index, you always address first element only!
So to retrieve our seven field array, we only need to re-set 1st element:
a=36
declare -p a
declare -a a='([0]="36" [1]="18" [2]="one" [3]="word" [4]="hello world!" [5]="he
llo world!" [6]="hello world!")'
One argument format string with many argument passed to:
printf -v a[0] '<%s>\n' "${a[@]}"
echo "$a"
<36>
<18>
<one>
<word>
<hello world!>
<hello world!>
<hello world!>
Nota: The use of double-quotes may be useful for manipulating strings that contain spaces
, tabulations
and/or newlines
Under POSIX shell, you could not use bashisms, so there is no builtin printf
.
But you could simply do:
printf
If you want to use more sophisticated constructions you have to use a fork (new child process that make the job and return the result via stdout
):
Historically, you could use backticks for retrieving result of a fork:
But this is not easy for nesting:
foo="Today is: "
foo=$(printf "%s %s" "$foo" "$(date)")
echo $foo
Today is: Sun Aug 4 11:58:23 CEST 2013
with backticks, you have to escape inner forks with backslashes:
foo="Today is: "
foo=`printf "%s %s" "$foo" "\`date\`"`
echo $foo
Today is: Sun Aug 4 11:59:10 CEST 2013
I’ve used the following script to see if a file exists:
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File $FILE exists."
else
echo "File $FILE does not exist."
fi
What’s the correct syntax to use if I only want to check if the file does not exist?
The test command ([
here) has a “not” logical operator which is the exclamation point (similar to many other languages). Try this:
Bash File Testing
-b filename
- Block special file
-c filename
- Special character file
-d directoryname
- Check for directory Existence
-e filename
- Check for file existence, regardless of type (node, directory, socket, etc.)
-f filename
- Check for regular file existence not a directory
-G filename
- Check if file exists and is owned by effective group ID
-G filename set-group-id
- True if file exists and is set-group-id
-k filename
- Sticky bit
-L filename
- Symbolic link
-O filename
- True if file exists and is owned by the effective user id
-r filename
- Check if file is a readable
-S filename
- Check if file is socket
-s filename
- Check if file is nonzero size
-u filename
- Check if file set-user-id bit is set
-w filename
- Check if file is writable
-x filename
- Check if file is executable
How to use:
#!/bin/bash
file=./file
if [ -e "$file" ]; then
echo "File exists"
else
echo "File does not exist"
fi
A test expression can be negated by using the !
operator
You can negate an expression with “!”:
The relevant man page is man test
or, equivalently, man [
– or help test
or help [
for the built-in bash command.
I have this string stored in a variable:
Now I would like to split the strings by ;
delimiter so that I have:
I don’t necessarily need the ADDR1
and ADDR2
variables. If they are elements of an array that’s even better.
After suggestions from the answers below, I ended up with the following which is what I was after:
#!/usr/bin/env bash
IN="bla@some.com;john@home.com"
mails=$(echo $IN | tr ";" "\n")
for addr in $mails
do
echo "> [$addr]"
done
Output:
There was a solution involving setting Internal_field_separator (IFS) to ;
. I am not sure what happened with that answer, how do you reset IFS
back to default?
RE: IFS
solution, I tried this and it works, I keep the old IFS
and then restore it:
IN="bla@some.com;john@home.com"
OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
echo "> [$x]"
done
IFS=$OIFS
BTW, when I tried
I only got the first string when printing it in loop, without brackets around $IN
it works.
You can set the internal field separator (IFS) variable, and then let it parse into an array. When this happens in a command, then the assignment to IFS
only takes place to that single command’s environment (to read
). It then parses the input according to the IFS
variable value into an array, which we can then iterate over.
It will parse one line of items separated by ;
, pushing it into an array. Stuff for processing whole of $IN
, each time one line of input separated by ;
:
Taken from Bash shell script split array:
Explanation:
This construction replaces all occurrences of ';'
(the initial //
means global replace) in the string IN
with ' '
(a single space), then interprets the space-delimited string as an array (that’s what the surrounding parentheses do).
The syntax used inside of the curly braces to replace each ';'
character with a ' '
character is called Parameter Expansion.
There are some common gotchas:
If you don’t mind processing them immediately, I like to do this:
You could use this kind of loop to initialize an array, but there’s probably an easier way to do it. Hope this helps, though.
In Bash, tried this:
But it doesn’t print a newline, only \n
. How can I make it print the newline?
I’m using Ubuntu 11.04.
You could use printf
instead:
printf
has more consistent behavior than echo
. The behavior of echo
varies greatly between different versions.
Are you sure you are in bash? Works for me, all three ways:
prints
$''
strings use ANSI C Quoting:
Words of the form $'<i>string</i>'
are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard.
I have a string in Bash:
How can I test if it contains another string?
Where ??
is my unknown operator. Do I use echo and grep
?
That looks a bit clumsy.
You can use Marcus’s answer (* wildcards) outside a case statement, too, if you use double brackets:
Note that spaces in the needle string need to be placed between double quotes, and the *
wildcards should be outside.
If you prefer the regex approach:
I am not sure about using an if statement, but you can get a similar effect with a case statement:
I have a pretty simple script that is something like the following:
When I run this script from the command line and pass it the arguments, I am not getting any output. However, when I run the commands contained within the $MOREF
variable, I am able to get output.
How can one take the results of a command that needs to be run within a script, save it to a variable, and then output that variable on the screen?
In addition to backticks `command`
you can use $(command)
or "$(command)"
which I find easier to read, and allow for nesting.
Quoting ("
) does matter to preserve multi-line values.
The right way is
If you’re going to use an apostrophe, you need ``, not
’`. This character is called “backticks” (or “grave accent”).
Like this:
As they have already indicated to you, you should use ‘backticks’.
The alternative proposed $(command)
works as well, and it also easier to read, but note that it is valid only with Bash or KornShell (and shells derived from those), so if your scripts have to be really portable on various Unix systems, you should prefer the old backticks notation.
What’s the easiest way to create a file in Linux terminal?
Depending on what you want the file to contain:
touch /path/to/file
for an empty file
somecommand > /path/to/file
for a file containing the output of some command.
sh eg: grep --help > randomtext.txt echo "This is some text" > randomtext.txt
nano /path/to/file
or vi /path/to/file
(or any other editor emacs,gedit etc
)
It either opens the existing one for editing or creates & opens the empty file to enter, if it doesn’t exist
Use touch
Create the file using cat
$ cat > myfile.txt
Now, just type whatever you want in the file:
Hello World!
CTRL-D to save and exit
How do I get the path of the directory in which a Bash script is located, inside that script?
For instance, let’s say I want to use a Bash script as a launcher for another application. I want to change the working directory to the one where the Bash script is located, so I can operate on the files in that directory, like so:
$ ./application
is a useful one-liner which will give you the full directory name of the script no matter where it is being called from.
It will work as long as the last component of the path used to find the script is not a symlink (directory links are OK). If you also want to resolve any links to the script itself, you need a multi-line solution:
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
This last one will work with any combination of aliases, source
, bash -c
, symlinks, etc.
Beware: if you cd
to a different directory before running this snippet, the result may be incorrect!
Also, watch out for $CDPATH
gotchas, and stderr output side effects if the user has smartly overridden cd to redirect output to stderr instead (including escape sequences, such as when calling update_terminal_cwd >&2
on Mac). Adding >/dev/null 2>&1
at the end of your cd
command will take care of both possibilities.
To understand how it works, try running this more verbose form:
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
TARGET="$(readlink "$SOURCE")"
if [[ $TARGET == /* ]]; then
echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
SOURCE="$TARGET"
else
DIR="$( dirname "$SOURCE" )"
echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"
And it will print something like:
Use dirname "$0"
:
#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"
using pwd
alone will not work if you are not running the script from the directory it is contained in.
[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp
The dirname command is the most basic, simply parsing the path up to the filename off of the $0 (script name) variable:
But, as matt b pointed out, the path returned is different depending on how the script is called. pwd doesn’t do the job because that only tells you what the current directory is, not what directory the script resides in. Additionally, if a symbolic link to a script is executed, you’re going to get a (probably relative) path to where the link resides, not the actual script.
Some others have mentioned the readlink command, but at its simplest, you can use:
readlink will resolve the script path to an absolute path from the root of the filesystem. So, any paths containing single or double dots, tildes and/or symbolic links will be resolved to a full path.
Here’s a script demonstrating each of these, whatdir.sh:
#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"
Running this script in my home dir, using a relative path:
>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat
Again, but using the full path to the script:
>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
Now changing directories:
>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
And finally using a symbolic link to execute the script:
>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
I have the following .txt file:
I want to read it line-by-line, and for each line I want to assign a .txt line value to a variable. Supposing my variable is $name
, the flow is:
$name
= “Marco”
$name
$name
= “Paolo”
The following reads a file passed as an argument line by line:
This is the standard form for reading lines from a file in a loop. Explanation:
IFS=
(or IFS=''
) prevents leading/trailing whitespace from being trimmed.
-r
prevents backslash escapes from being interpreted.
If the above is saved to a script with filename readfile
, it can be run as follows:
If the file isn’t a standard POSIX text file (= not terminated by a newline character), the loop can be modified to handle trailing partial lines:
Here, || [[ -n $line ]]
prevents the last line from being ignored if it doesn’t end with a \n
(since read
returns a non-zero exit code when it encounters EOF).
If the commands inside the loop also read from standard input, the file descriptor used by read
can be chanced to something else (avoid the standard file descriptors), e.g.:
(Non-Bash shells might not know read -u3
; use read <&3
instead.)
I encourage you to use the -r
flag for read
which stands for:
-r Do not treat a backslash character in any special way. Consider each
backslash to be part of the input line.
I am citing from man 1 read
.
Another thing is to take a filename as an argument.
Here is updated code:
Using the following Bash template should allow you to read one value at a time from a file and process it.
I’m trying to add a directory to my path so it will always be in my Linux path. I’ve tried:
This works, however each time I exit the terminal and start a new terminal instance, this path is lost, and I need to run the export command again.
How can I do it so this will be set permanently?
You need to add it to your ~/.profile
or ~/.bashrc
file.
Depending on what you’re doing, you also may want to symlink to binaries:
Note that this will not automatically update your path for the remainder of the session. To do this, you should run:
In Ubuntu, edit /etc/environment
. Its sole purpose is to store Environment Variables. Originally the $PATH variable is defined here. This is a paste from my /etc/environment
file:
So you can just open up this file as root and add whatever you want.
For Immediate results, Run (try as normal user and root):
UPDATE:
If you use zsh
(a.k.a Z Shell), add this line right after the comments in /etc/zsh/zshenv
:
I encountered this little quirk on Ubuntu 15.10, but if your zsh is not getting the correct PATH, this could be why
There are multiple ways to do it. The actual solution depends on the purpose.
The variable values are usually stored in either a list of assignments or a shell script that is run at the start of the system or user session. In case of the shell script you must use a specific shell syntax.
/etc/environment
List of unique assignments. Perfect for adding system-wide directories like /usr/local/something/bin
to PATH
variable or defining JAVA_HOME
.
/etc/xprofile
Shell script executed while starting X Window System session. This is run for every user that logs into X Window System. It is a good choice for PATH
entries that are valid for every user like /usr/local/something/bin
. The file is included by other script so use POSIX shell syntax not the syntax of your user shell.
/etc/profile
and /etc/profile.d/*
Shell script. This is a good choice for shell-only systems. Those files are read only by shells.
/etc/<shell>.<shell>rc
. Shell script. This is a poor choice because it is single shell specific.
~/.pam_environment
. List of unique assignments. Loaded by PAM at the start of every user session irrelevant if it is an X Window System session or shell. You cannot reference other variable including HOME
or PATH
so it has limited use.
~/.xprofile
Shell script. This is executed when the user logs into X Window System system. The variables defined here are visible to every X application. Perfect choice for extending PATH
with values such as ~/bin
or ~/go/bin
or defining user specific GOPATH
or NPM_HOME
. The file is included by other script so use POSIX shell syntax not the syntax of your user shell. Your graphical text editor or IDE started by shortcut will see those values.
~/.profile
Shell script. It will be visible only for programs started from terminal or terminal emulator. It is a good choice for shell-only systems.
~/.<shell>rc
. Shell script. This is a poor choice because it is single shell specific.
How do I iterate through each line of a text file with Bash?
With this script:
I get this output on the screen:
Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'
(Later I want to do something more complicated with $p
than just output to the screen.)
The environment variable SHELL is (from env):
/bin/bash --version
output:
GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.
cat /proc/version
output:
The file peptides.txt contains:
One way to do it is:
As pointed out in the comments, this has the side effects of trimming leading whitespace, interpretting backslash sequences, and skipping the trailing line if it’s missing a terminating linefeed. If these are concerns, you can do:
Exceptionally, if the loop body may read from standard input, you can open the file using a different file descriptor:
Here, 10 is just an arbitrary number (different from 0, 1, 2).
and the one-liner variant:
Option 1a: While loop: Single line at a time: Input redirection
Option 1b: While loop: Single line at a time:
Open the file, read from a file descriptor (in this case file descriptor #4).
I want to get the filename (without extension) and the extension separately.
The best solution I found so far is:
This is wrong because it doesn’t work if the file name contains multiple .
characters. If, let’s say, I have a.b.js
, it will consider a
and b.js
, instead of a.b
and js
.
It can be easily done in Python with
but I’d prefer not to fire up a Python interpreter just for this, if possible.
Any better ideas?
First, get file name without the path:
Alternatively, you can focus on the last ‘/’ of the path instead of the ‘.’ which should work even if you have unpredictable file extensions:
You may want to check the documentation :
~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz
For more details, see shell parameter expansion in the Bash manual.
Usually you already know the extension, so you might wish to use:
for example:
and we get
Say, I have a script that gets called with this line:
or this one:
What’s the accepted way of parsing this such that in each case (or some combination of the two) $v
, $f
, and $d
will all be set to true
and $outFile
will be equal to /fizz/someOtherFile
?
Update: It’s been more than 5 years since I started this answer. Thank you for LOTS of great edits/comments/suggestions. In order save maintenance time, I’ve modified the code block to be 100% copy-paste ready. Please do not post comments like “What if you changed X to Y…”. Instead, copy-paste the code block, see the output, make the change, rerun the script, and comment “I changed X to Y and…” I don’t have time to test your ideas and tell you if they work.
Two common ways to pass key-value-pair arguments are:
--option argument
) (without getopt[s])
Usage demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-e|--extension)
EXTENSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
-l|--lib)
LIBPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 "$1"
fi
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
output from copy-pasting the block above:
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
--option=argument
) (without getopt[s])
Usage demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash
for i in "$@"
do
case $i in
-e=*|--extension=*)
EXTENSION="${i#*=}"
shift # past argument=value
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
shift # past argument=value
;;
-l=*|--lib=*)
LIBPATH="${i#*=}"
shift # past argument=value
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
*)
# unknown option
;;
esac
done
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 $1
fi
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
output from copy-pasting the block above:
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
To better understand ${i#*=}
search for “Substring Removal” in this guide. It is functionally equivalent to sed 's/[^=]*=//' <<< "$i"
which calls a needless subprocess or echo "$i" | sed 's/[^=]*=//'
which calls two needless subprocesses.
from: http://mywiki.wooledge.org/BashFAQ/035#getopts
getopt(1) limitations (older, relatively-recent getopt
versions):
More recent getopt
versions don’t have these limitations.
Additionally, the POSIX shell (and others) offer getopts
which doesn’t have these limitations. I’ve included a simplistic getopts
example.
Usage demo-getopts.sh -vf /etc/hosts foo bar
cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh
# A POSIX variable
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
output_file=""
verbose=0
while getopts "h?vf:" opt; do
case "$opt" in
h|\?)
show_help
exit 0
;;
v) verbose=1
;;
f) output_file=$OPTARG
;;
esac
done
shift $((OPTIND-1))
[ "${1:-}" = "--" ] && shift
echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF
chmod +x /tmp/demo-getopts.sh
/tmp/demo-getopts.sh -vf /etc/hosts foo bar
output from copy-pasting the block above:
The advantages of getopts
are:
dash
.
-vf filename
in the typical Unix way, automatically.
The disadvantage of getopts
is that it can only handle short options (-h
, not --help
) without additional code.
There is a getopts tutorial which explains what all of the syntax and variables mean. In bash, there is also help getopts
, which might be informative.
No answer mentions enhanced getopt. And the top-voted answer is misleading: It either ignores -vfd
style short options (requested by the OP) or options after positional arguments (also requested by the OP); and it ignores parsing-errors. Instead:
getopt
from util-linux or formerly GNU glibc.1
getopt_long()
the C function of GNU glibc.
Has all useful distinguishing features (the others don’t have them):
getopt
can’t do this)
script.sh -o outFile file1 file2 -v
(getopts
doesn’t do this)
=
-style long options: script.sh --outfile=fileOut --infile fileIn
(allowing both is lengthy if self parsing)
-vfd
(real work if self parsing)
-oOutfile
or -vfdoOutfile
getopt --test
→ return value 4.
getopt
or shell-builtin getopts
are of limited use.
The following calls
myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
all return
with the following myscript
#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset
# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
echo 'I’m sorry, `getopt --test` failed in this environment.'
exit 1
fi
OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose
# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via -- "$@" to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
# e.g. return value is 1
# then getopt has complained about wrong arguments to stdout
exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"
d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
case "$1" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile="$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo "$0: A single input file is required."
exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
1 enhanced getopt is available on most “bash-systems”, including Cygwin; on OS X try brew install gnu-getopt or sudo port install getopt
2 the POSIX exec()
conventions have no reliable way to pass binary NULL in command line arguments; those bytes prematurely end the argument
3 first version released in 1997 or before (I only tracked it back to 1997)
from : digitalpeer.com with minor modifications
Usage myscript.sh -p=my_prefix -s=dirname -l=libname
#!/bin/bash
for i in "$@"
do
case $i in
-p=*|--prefix=*)
PREFIX="${i#*=}"
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
;;
-l=*|--lib=*)
DIR="${i#*=}"
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}
To better understand ${i#*=}
search for “Substring Removal” in this guide. It is functionally equivalent to sed 's/[^=]*=//' <<< "$i"
which calls a needless subprocess or echo "$i" | sed 's/[^=]*=//'
which calls two needless subprocesses.
I tried using $(date)
in my bash shell script, however I want the date in YYYY-MM-DD format. How do I get this?
In bash (>=4.2) it is preferable to use printf’s built-in date formatter (part of bash) rather than the external date
(usually GNU date).
As such:
# put current date as yyyy-mm-dd in $date
# -1 -> explicit current date, bash >=4.3 defaults to current time if not provided
# -2 -> start time for shell
printf -v date '%(%Y-%m-%d)T\n' -1
# put current date as yyyy-mm-dd HH:MM:SS in $date
printf -v date '%(%Y-%m-%d %H:%M:%S)T\n' -1
# to print directly remove -v flag, as such:
printf '%(%Y-%m-%d)T\n' -1
# -> current date printed to terminal
In bash (<4.2):
# put current date as yyyy-mm-dd in $date
date=$(date '+%Y-%m-%d')
# put current date as yyyy-mm-dd HH:MM:SS in $date
date=$(date '+%Y-%m-%d %H:%M:%S')
# print current date directly
echo $(date '+%Y-%m-%d')
Other available date formats can be viewed from the date man pages (for external non-bash specific command):
Try: $(date +%F)
You can do something like this:
I have lines like these, and I want to know how many lines I actually have…
09:16:39 AM all 2.00 0.00 4.00 0.00 0.00 0.00 0.00 0.00 94.00
09:16:40 AM all 5.00 0.00 0.00 4.00 0.00 0.00 0.00 0.00 91.00
09:16:41 AM all 0.00 0.00 4.00 0.00 0.00 0.00 0.00 0.00 96.00
09:16:42 AM all 3.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 96.00
09:16:43 AM all 0.00 0.00 1.00 0.00 1.00 0.00 0.00 0.00 98.00
09:16:44 AM all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
09:16:45 AM all 2.00 0.00 6.00 0.00 0.00 0.00 0.00 0.00 92.00
Is there a way to count them all using linux commands?
Use wc
:
This will output the number of lines in <filename>
:
Or, to omit the <filename>
from the result use wc -l < <filename>
:
You can also pipe data to wc
as well:
To count all lines use:
To filter and count only lines with pattern use:
Or use -v to invert match:
See the grep man page to take a look at the -e,-i and -x args…
Or
I have a shell script with this code:
But the conditional code always executes, because hg st
always prints at least one newline character.
$var
(like trim()
in PHP)?
or
I could use sed or AWK, but I’d like to think there is a more elegant solution to this problem.
Let’s define a variable containing leading, trailing, and intermediate whitespace:
FOO=' test test test '
echo -e "FOO='${FOO}'"
# > FOO=' test test test '
echo -e "length(FOO)==${#FOO}"
# > length(FOO)==16
How to remove all whitespace (denoted by [:space:]
in tr
):
FOO=' test test test '
FOO_NO_WHITESPACE="$(echo -e "${FOO}" | tr -d '[:space:]')"
echo -e "FOO_NO_WHITESPACE='${FOO_NO_WHITESPACE}'"
# > FOO_NO_WHITESPACE='testtesttest'
echo -e "length(FOO_NO_WHITESPACE)==${#FOO_NO_WHITESPACE}"
# > length(FOO_NO_WHITESPACE)==12
How to remove leading whitespace only:
FOO=' test test test '
FOO_NO_LEAD_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//')"
echo -e "FOO_NO_LEAD_SPACE='${FOO_NO_LEAD_SPACE}'"
# > FOO_NO_LEAD_SPACE='test test test '
echo -e "length(FOO_NO_LEAD_SPACE)==${#FOO_NO_LEAD_SPACE}"
# > length(FOO_NO_LEAD_SPACE)==15
How to remove trailing whitespace only:
FOO=' test test test '
FOO_NO_TRAIL_SPACE="$(echo -e "${FOO}" | sed -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_TRAIL_SPACE='${FOO_NO_TRAIL_SPACE}'"
# > FOO_NO_TRAIL_SPACE=' test test test'
echo -e "length(FOO_NO_TRAIL_SPACE)==${#FOO_NO_TRAIL_SPACE}"
# > length(FOO_NO_TRAIL_SPACE)==15
How to remove both leading and trailing spaces–chain the sed
s:
FOO=' test test test '
FOO_NO_EXTERNAL_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_EXTERNAL_SPACE='${FOO_NO_EXTERNAL_SPACE}'"
# > FOO_NO_EXTERNAL_SPACE='test test test'
echo -e "length(FOO_NO_EXTERNAL_SPACE)==${#FOO_NO_EXTERNAL_SPACE}"
# > length(FOO_NO_EXTERNAL_SPACE)==14
Alternatively, if your bash supports it, you can replace echo -e "${FOO}" | sed ...
with sed ... <<<${FOO}
, like so (for trailing whitespace):
A simple answer is:
Xargs will do the trimming for you. It’s one command/program, no parameters, returns the trimmed string, easy as that!
Note: this doesn’t remove the internal spaces so "foo bar"
stays the same. It does NOT become "foobar"
.
How do I know if a variable is set in Bash?
For example, how do I check if the user gave the first parameter to a function?
where ${var+x}
is a parameter expansion which evaluates to nothing if var
is unset, and substitutes the string x
otherwise.
Quotes can be omitted (so we can say ${var+x}
instead of "${var+x}"
) because this syntax & usage guarantees this will only expand to something that does not require quotes (since it either expands to x
(which contains no word breaks so it needs no quotes), or to nothing (which results in [ -z ]
, which conveniently evaluates to the same value (true) that [ -z "" ]
does as well)).
However, while quotes can be safely omitted, and it was not immediately obvious to all (it wasn’t even apparent to the first author of this quotes explanation who is also a major Bash coder), it would sometimes be better to write the solution with quotes as [ -z "${var+x}" ]
, at the very small possible cost of an O(1) speed penalty. The first author also added this as a comment next to the code using this solution giving the URL to this answer, which now also includes the explanation for why the quotes can be safely omitted.
This is often wrong because it doesn’t distinguish between a variable that is unset and a variable that is set to the empty string. That is to say, if var=''
, then the above solution will output “var is blank”.
The distinction between unset and “set to the empty string” is essential in situations where the user has to specify an extension, or additional list of properties, and that not specifying them defaults to a non-empty value, whereas specifying the empty string should make the script use an empty extension or list of additional properties.
The distinction may not be essential in every scenario though. In those cases [ -z "$var" ]
will be just fine.
To check for non-null/non-zero string variable, i.e. if set, use
It’s the opposite of -z
. I find myself using -n
more than -z
.
You would use it like:
Here’s how to test whether a parameter is unset, or empty (“Null”) or set with a value:
+--------------------+----------------------+-----------------+-----------------+
| | parameter | parameter | parameter |
| | Set and Not Null | Set But Null | Unset |
+--------------------+----------------------+-----------------+-----------------+
| ${parameter:-word} | substitute parameter | substitute word | substitute word |
| ${parameter-word} | substitute parameter | substitute null | substitute word |
| ${parameter:=word} | substitute parameter | assign word | assign word |
| ${parameter=word} | substitute parameter | substitute null | assign word |
| ${parameter:?word} | substitute parameter | error, exit | error, exit |
| ${parameter?word} | substitute parameter | substitute null | error, exit |
| ${parameter:+word} | substitute word | substitute null | substitute null |
| ${parameter+word} | substitute word | substitute word | substitute null |
+--------------------+----------------------+-----------------+-----------------+
Source: POSIX: Parameter Expansion:
In all cases shown with “substitute”, the expression is replaced with the value shown. In all cases shown with “assign”, parameter is assigned that value, which also replaces the expression.
How do I compare a variable to a string (and do something if they match)?
If you want to do something when they don’t match, replace =
with !=
. You can read more about string operations and arithmetic operations in their respective documentation.
$x
?
You want the quotes around $x
, because if it is empty, your bash script encounters a syntax error as seen below:
==
operator
Note that bash
allows ==
to be used for equality with [
, but this is not standard.
Use either the first case wherein the quotes around $x
are optional:
or use the second case:
Or, if you don’t need else clause:
a="abc"
b="def"
# Equality Comparison
if [ "$a" == "$b" ]; then
echo "Strings match"
else
echo "Strings don't match"
fi
# Lexicographic (greater than, less than) comparison.
if [ "$a" \< "$b" ]; then
echo "$a is lexicographically smaller then $b"
elif [ "$a" \> "$b" ]; then
echo "$b is lexicographically smaller than $a"
else
echo "Strings are equal"
fi
Notes:
if
and [
and ]
are important
>
and <
are redirection operators so escape it with \>
and \<
respectively for strings.
Given a filename in the form someletters_12345_moreleters.ext
, I want to extract the 5 digits and put them into a variable.
So to emphasize the point, I have a filename with x number of characters then a five digit sequence surrounded by a single underscore on either side then another set of x number of characters. I want to take the 5 digit number and put that into a variable.
I am very interested in the number of different ways that this can be accomplished.
Use cut:
More generic:
If x is constant, the following parameter expansion performs substring extraction:
where 12 is the offset (zero-based) and 5 is the length
If the underscores around the digits are the only ones in the input, you can strip off the prefix and suffix (respectively) in two steps:
If there are other underscores, it’s probably feasible anyway, albeit more tricky. If anyone knows how to perform both expansions in a single expression, I’d like to know too.
Both solutions presented are pure bash, with no process spawning involved, hence very fast.
Generic solution where the number can be anywhere in the filename, using the first of such sequences:
Another solution to extract exactly a part of a variable:
If your filename always have the format stuff_digits_...
you can use awk:
Yet another solution to remove everything except digits, use
I need to check the existence of an input argument. I have the following script
I get
How do I check the input argument1 first to see if it exists?
It is:
The $#
variable will tell you the number of input arguments the script was passed.
Or you can check if an argument is an empty string or not like:
The -z
switch will test if the expansion of “$1” is a null string or not. If it is a null string then the body is executed.
It is better to demonstrate this way
You normally need to exit if you have too few arguments.
In some cases you need to check whether the user passed an argument to the script and if not, fall back to a default value. Like in the script below:
Here if the user hasn’t passed scale
as a 2nd parameter, I launch Android emulator with -scale 1
by default. ${varname:-word}
is an expansion operator. There are other expansion operators as well:
${varname:=word}
which sets the undefined varname
instead of returning the word
value;
${varname:?message}
which either returns varname
if it’s defined and is not null or prints the message
and aborts the script (like the first example);
${varname:+word}
which returns word
only if varname
is defined and is not null; returns null otherwise.
I want to write a script that loops through 15 strings (array possibly?) Is that possible?
Something like:
You can use it like this:
## declare an array variable
declare -a arr=("element1" "element2" "element3")
## now loop through the above array
for i in "${arr[@]}"
do
echo "$i"
# or do whatever with individual element of the array
done
# You can access them using echo "${arr[0]}", "${arr[1]}" also
Also works for multi-line array declaration
That is possible, of course.
See Bash Loops for, while and until for details.
None of those answers include a counter…
#!/bin/bash
## declare an array variable
declare -a array=("one" "two" "three")
# get length of an array
arraylength=${#array[@]}
# use for loop to read all values and indexes
for (( i=1; i<${arraylength}+1; i++ ));
do
echo $i " / " ${arraylength} " : " ${array[$i-1]}
done
Output:
In a Unix shell, if I want to combine stderr
and stdout
into the stdout
stream for further manipulation, I can append the following on the end of my command:
So, if I want to use head
on the output from g++
, I can do something like this:
so I can see only the first few errors.
I always have trouble remembering this, and I constantly have to go look it up, and it is mainly because I don’t fully understand the syntax of this particular trick.
Can someone break this up and explain character by character what 2>&1
means?
File descriptor 1 is the standard output (stdout
).
File descriptor 2 is the standard error (stderr
).
Here is one way to remember this construct (although it is not entirely accurate): at first, 2>1
may look like a good way to redirect stderr
to stdout
. However, it will actually be interpreted as “redirect stderr
to a file named 1
”. &
indicates that what follows is a file descriptor and not a filename. So the construct becomes: 2>&1
.
redirects stdout to afile.txt
. This is the same as doing
To redirect stderr, you do:
>&
is the syntax to redirect a stream to another file descriptor - 0 is stdin, 1 is stdout, and 2 is stderr.
You can redirect stdout to stderr by doing:
Or vice versa:
So, in short… 2>
redirects stderr to an (unspecified) file, appending &1
redirects stderr to stdout.
Some syntax particularity about this may have important behaviours. There is some little samples about redirections, STDERR
, STDOUT
, and arguments ordering.
Symbol >
mean redirection.
>
mean send to as a whole completed file, overwriting target if exist (see noclobber
bash feature at #3 later).
>>
mean send in addition to would append to target if exist.
In any case, the file would be created if they not exist.
For testing this, we need a simple command which will send something on both outputs:
$ ls -ld /tmp /tnt
ls: cannot access /tnt: No such file or directory
drwxrwxrwt 118 root root 196608 Jan 7 11:49 /tmp
$ ls -ld /tmp /tnt >/dev/null
ls: cannot access /tnt: No such file or directory
$ ls -ld /tmp /tnt 2>/dev/null
drwxrwxrwt 118 root root 196608 Jan 7 11:49 /tmp
(Expecting you don’t have a directory named /tnt
, of course ;). Well, we have it!!
So, let’s see:
$ ls -ld /tmp /tnt >/dev/null
ls: cannot access /tnt: No such file or directory
$ ls -ld /tmp /tnt >/dev/null 2>&1
$ ls -ld /tmp /tnt 2>&1 >/dev/null
ls: cannot access /tnt: No such file or directory
The last command line dumps STDERR
to the console, and it seem not to be the expected behaviour… But…
If you want to make some post filtering about one output, the other or both:
$ ls -ld /tmp /tnt | sed 's/^.*$/<-- & --->/'
ls: cannot access /tnt: No such file or directory
<-- drwxrwxrwt 118 root root 196608 Jan 7 12:02 /tmp --->
$ ls -ld /tmp /tnt 2>&1 | sed 's/^.*$/<-- & --->/'
<-- ls: cannot access /tnt: No such file or directory --->
<-- drwxrwxrwt 118 root root 196608 Jan 7 12:02 /tmp --->
$ ls -ld /tmp /tnt >/dev/null | sed 's/^.*$/<-- & --->/'
ls: cannot access /tnt: No such file or directory
$ ls -ld /tmp /tnt >/dev/null 2>&1 | sed 's/^.*$/<-- & --->/'
$ ls -ld /tmp /tnt 2>&1 >/dev/null | sed 's/^.*$/<-- & --->/'
<-- ls: cannot access /tnt: No such file or directory --->
Notice that the last command line in this paragraph is exactly same as in previous paragraph, where I wrote seem not to be the expected behaviour (so, this could even be an expected behaviour).
Well, there is a little tricks about redirections, for doing different operation on both outputs:
$ ( ls -ld /tmp /tnt | sed 's/^/O: /' >&9 ) 9>&2 2>&1 | sed 's/^/E: /'
O: drwxrwxrwt 118 root root 196608 Jan 7 12:13 /tmp
E: ls: cannot access /tnt: No such file or directory
Nota: &9
descriptor would occur spontaneously because of ) 9>&2
.
Addendum: nota! With the new version of bash (>4.0
) there is a new feature and more sexy syntax for doing this kind of things:
$ ls -ld /tmp /tnt 2> >(sed 's/^/E: /') > >(sed 's/^/O: /')
O: drwxrwxrwt 17 root root 28672 Nov 5 23:00 /tmp
E: ls: cannot access /tnt: No such file or directory
And finally for such a cascading output formatting:
$ ((ls -ld /tmp /tnt |sed 's/^/O: /' >&9 ) 2>&1 |sed 's/^/E: /') 9>&1| cat -n
1 O: drwxrwxrwt 118 root root 196608 Jan 7 12:29 /tmp
2 E: ls: cannot access /tnt: No such file or directory
Addendum: nota! Same new syntax, in both ways:
$ cat -n <(ls -ld /tmp /tnt 2> >(sed 's/^/E: /') > >(sed 's/^/O: /'))
1 O: drwxrwxrwt 17 root root 28672 Nov 5 23:00 /tmp
2 E: ls: cannot access /tnt: No such file or directory
Where STDOUT
go through a specific filter, STDERR
to another and finally both outputs merged go through a third command filter.
noclobber
option and >|
syntax
That’s about overwriting:
While set -o noclobber
instruct bash to not overwrite any existing file, the >|
syntax let you pass through this limitation:
$ testfile=$(mktemp /tmp/testNoClobberDate-XXXXXX)
$ date > $testfile ; cat $testfile
Mon Jan 7 13:18:15 CET 2013
$ date > $testfile ; cat $testfile
Mon Jan 7 13:18:19 CET 2013
$ date > $testfile ; cat $testfile
Mon Jan 7 13:18:21 CET 2013
The file is overwritten each time, well now:
$ set -o noclobber
$ date > $testfile ; cat $testfile
bash: /tmp/testNoClobberDate-WW1xi9: cannot overwrite existing file
Mon Jan 7 13:18:21 CET 2013
$ date > $testfile ; cat $testfile
bash: /tmp/testNoClobberDate-WW1xi9: cannot overwrite existing file
Mon Jan 7 13:18:21 CET 2013
Pass through with >|
:
$ date >| $testfile ; cat $testfile
Mon Jan 7 13:18:58 CET 2013
$ date >| $testfile ; cat $testfile
Mon Jan 7 13:19:01 CET 2013
Unsetting this option and/or inquiring if already set.
$ set -o | grep noclobber
noclobber on
$ set +o noclobber
$ set -o | grep noclobber
noclobber off
$ date > $testfile ; cat $testfile
Mon Jan 7 13:24:27 CET 2013
$ rm $testfile
For redirecting both output from a given command, we see that a right syntax could be:
for this special case, there is a shortcut syntax: &>
… or >&
Nota: if 2>&1
exist, 1>&2
is a correct syntax too:
$ ls -ld /tmp /tnt 2>&1 1>&2 | sed -e s/^/++/
++/bin/ls: cannot access /tnt: No such file or directory
++drwxrwxrwt 193 root root 196608 Feb 9 11:08 /tmp/
$ ls -ld /tmp /tnt 1>&2 2>&1 | sed -e s/^/++/
/bin/ls: cannot access /tnt: No such file or directory
drwxrwxrwt 193 root root 196608 Feb 9 11:08 /tmp/
You could read the fine manual by hitting:
in a bash console ;-)
I know that in Linux, to redirect output from the screen to a file, I can either use the >
or tee
. However, I’m not sure why part of the output is still output to the screen and not written to the file.
Is there a way to redirect all output to file?
That part is written to stderr, use 2>
to redirect it. For example:
or if you want in same file:
Note: this works in (ba)sh, check your shell for proper syntax
All POSIX operating systems have 3 streams: stdin, stdout, and stderr. stdin is the input, which can accept the stdout or stderr. stdout is the primary output, which is redirected with >
, >>
, or |
. stderr is the error output, which is handled separately so that any exceptions do not get passed to a command or written to a file that it might break; normally, this is sent to a log of some kind, or dumped directly, even when the stdout is redirected. To redirect both to the same place, use:
<em>command</em> &> /some/file
EDIT: thanks to Zack for pointing out that the above solution is not portable–use instead:
If you want to silence the error, do:
To get the output on the console AND in a file file.txt
for example.
Note: &
(in 2>&1
) specifies that 1
is not a file name but a file descriptor.
I’m trying to get an if
statement to work in Bash (using Ubuntu):
I’ve tried various forms of the if
statement, using [["$s1" == "$s2"]]
, with and without quotes, using =
, ==
and -eq
, but I still get the following error:
[hi: command not found
I’ve looked at various sites and tutorials and copied those, but it doesn’t work - what am I doing wrong?
Eventually, I want to say if $s1
contains $s2
, so how can I do that?
I did just work out the spaces bit.. :/ How do I say contains?
I tried
but it didn’t work.
For string comparison, use:
For the a
contains b
, use:
(and make sure to add spaces between the symbols):
bad:
good:
You need spaces:
You should be careful to leave a space between the sign of ‘[’ and double quotes where the variable contains this:
The ^
s show the blank spaces you need to leave.
How do I iterate over a range of numbers in Bash when the range is given by a variable?
I know I can do this (called “sequence expression” in the Bash documentation):
Which gives:
1
2
3
4
5
Yet, how can I replace either of the range endpoints with a variable? This doesn’t work:
Which prints:
{1..5}
sh for i in $(seq 1 $END); do echo $i; done
edit: I prefer seq
over the other methods because I can actually remember it ;)
The seq
method is the simplest, but Bash has built-in arithmetic evaluation.
The for ((expr1;expr2;expr3));
construct works just like for (expr1;expr2;expr3)
in C and similar languages, and like other ((expr))
cases, Bash treats them as arithmetic.
Using seq
is fine, as Jiaaro suggested. Pax Diablo suggested a Bash loop to avoid calling a subprocess, with the additional advantage of being more memory friendly if $END is too large. Zathrus spotted a typical bug in the loop implementation, and also hinted that since i
is a text variable, continuous conversions to-and-fro numbers are performed with an associated slow-down.
This is an improved version of the Bash loop:
If the only thing that we want is the echo
, then we could write echo $((i++))
.
ephemient taught me something: Bash allows for ((expr;expr;expr))
constructs. Since I’ve never read the whole man page for Bash (like I’ve done with the Korn shell (ksh
) man page, and that was a long time ago), I missed that.
So,
seems to be the most memory-efficient way (it won’t be necessary to allocate memory to consume seq
’s output, which could be a problem if END is very large), although probably not the “fastest”.
eschercycle noted that the {a..b} Bash notation works only with literals; true, accordingly to the Bash manual. One can overcome this obstacle with a single (internal) fork()
without an exec()
(as is the case with calling seq
, which being another image requires a fork+exec):
Both eval
and echo
are Bash builtins, but a fork()
is required for the command substitution (the $(…)
construct).
I am trying to search how to pass parameters in a Bash function, but what comes up is always how to pass parameter from the command line.
I would like to pass parameters within my script. I tried:
myBackupFunction("..", "...", "xx")
function myBackupFunction($directory, $options, $rootPassword) {
...
}
But the syntax is not correct, how to pass a parameter to my function?
There are two typical ways of declaring a function. I prefer the second approach.
or
To call a function with arguments:
The function refers to passed arguments by their position (not by name), that is $1, $2, and so forth. $0 is the name of the script itself.
Example:
Also, you need to call your function after it is declared.
#!/usr/bin/env sh
foo 1 # this will fail because foo has not been declared yet.
foo() {
echo "Parameter #1 is $1"
}
foo 2 # this will work.
Output:
Knowledge of high level programming languages (C/C++/Java/PHP/Python/Perl …) would suggest to the layman that bash functions should work like they do in those other languages. Instead, bash functions work like shell commands and expect arguments to be passed to them in the same way one might pass an option to a shell command (ls -l). In effect, function arguments in bash are treated as positional parameters ($1, $2..$9, ${10}, ${11}
, and so on). This is no surprise considering how getopts
works. Parentheses are not required to call a function in bash.
(Note: I happen to be working on Open Solaris at the moment.)
# bash style declaration for all you PHP/JavaScript junkies. :-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
function backupWebRoot ()
{
tar -cvf - $1 | zip -n .jpg:.gif:.png $2 - 2>> $errorlog &&
echo -e "\nTarball created!\n"
}
# sh style declaration for the purist in you. ;-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
backupWebRoot ()
{
tar -cvf - $1 | zip -n .jpg:.gif:.png $2 - 2>> $errorlog &&
echo -e "\nTarball created!\n"
}
#In the actual shell script
#$0 $1 $2
backupWebRoot ~/public/www/ webSite.tar.zip
If you prefer named parameters, it’s possible (with a few tricks) to actually pass named parameters to functions (also makes it possible to pass arrays and references).
The method I developed allows you to define named parameters passed to a function like this:
function example { args : string firstName , string lastName , integer age } {
echo "My name is ${firstName} ${lastName} and I am ${age} years old."
}
You can also annotate arguments as @required or @readonly, create …rest arguments, create arrays from sequential arguments (using e.g. string[4]
) and optionally list the arguments in multiple lines:
function example {
args
: @required string firstName
: string lastName
: integer age
: string[] ...favoriteHobbies
echo "My name is ${firstName} ${lastName} and I am ${age} years old."
echo "My favorite hobbies include: ${favoriteHobbies[*]}"
}
In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays (and references to variables - this feature works only in bash 4.3 though)! Plus, the mapped variables are all in the local scope, just as $1 (and others).
The code that makes this work is pretty light and works both in bash 3 and bash 4 (these are the only versions I’ve tested it with). If you’re interested in more tricks like this that make developing with bash much nicer and easier, you can take a look at my Bash Infinity Framework, the code below is available as one of its functionalities.
shopt -s expand_aliases
function assignTrap {
local evalString
local -i paramIndex=${__paramIndex-0}
local initialCommand="${1-}"
if [[ "$initialCommand" != ":" ]]
then
echo "trap - DEBUG; eval \"${__previousTrap}\"; unset __previousTrap; unset __paramIndex;"
return
fi
while [[ "${1-}" == "," || "${1-}" == "${initialCommand}" ]] || [[ "${#@}" -gt 0 && "$paramIndex" -eq 0 ]]
do
shift # first colon ":" or next parameter's comma ","
paramIndex+=1
local -a decorators=()
while [[ "${1-}" == "@"* ]]
do
decorators+=( "$1" )
shift
done
local declaration=
local wrapLeft='"'
local wrapRight='"'
local nextType="$1"
local length=1
case ${nextType} in
string | boolean) declaration="local " ;;
integer) declaration="local -i" ;;
reference) declaration="local -n" ;;
arrayDeclaration) declaration="local -a"; wrapLeft= ; wrapRight= ;;
assocDeclaration) declaration="local -A"; wrapLeft= ; wrapRight= ;;
"string["*"]") declaration="local -a"; length="${nextType//[a-z\[\]]}" ;;
"integer["*"]") declaration="local -ai"; length="${nextType//[a-z\[\]]}" ;;
esac
if [[ "${declaration}" != "" ]]
then
shift
local nextName="$1"
for decorator in "${decorators[@]}"
do
case ${decorator} in
@readonly) declaration+="r" ;;
@required) evalString+="[[ ! -z \$${paramIndex} ]] || echo \"Parameter '$nextName' ($nextType) is marked as required by '${FUNCNAME[1]}' function.\"; " >&2 ;;
@global) declaration+="g" ;;
esac
done
local paramRange="$paramIndex"
if [[ -z "$length" ]]
then
# ...rest
paramRange="{@:$paramIndex}"
# trim leading ...
nextName="${nextName//\./}"
if [[ "${#@}" -gt 1 ]]
then
echo "Unexpected arguments after a rest array ($nextName) in '${FUNCNAME[1]}' function." >&2
fi
elif [[ "$length" -gt 1 ]]
then
paramRange="{@:$paramIndex:$length}"
paramIndex+=$((length - 1))
fi
evalString+="${declaration} ${nextName}=${wrapLeft}\$${paramRange}${wrapRight}; "
# continue to the next param:
shift
fi
done
echo "${evalString} local -i __paramIndex=${paramIndex};"
}
alias args='local __previousTrap=$(trap -p DEBUG); trap "eval \"\$(assignTrap \$BASH_COMMAND)\";" DEBUG;'
A rather unusual situation perhaps, but I want to specify a private SSH-key to use when executing a shell (git) command from the local computer.
Basically like this:
Or even better (in Ruby):
with_key("/home/christoffer/ssh_keys/theuser") do
sh("git clone git@github.com:TheUser/TheProject.git")
end
I have seen examples of connecting to a remote server with Net::SSH that uses a specified private key, but this is a local command. Is it possible?
Something like this should work (suggested by orip):
if you prefer subshells, you could try the following (though it is more fragile):
Git will invoke SSH which will find its agent by environment variable; this will, in turn, have the key loaded.
Alternatively, setting HOME
may also do the trick, provided you are willing to setup a directory that contains only a .ssh
directory as HOME
; this may either contain an identity.pub, or a config file setting IdentityFile.
None of these solutions worked for me.
Instead, I elaborate on @Martin v. Löwis ’s mention of setting a config
file for SSH.
SSH will look for the user’s ~/.ssh/config
file. I have mine setup as:
Host gitserv
Hostname remote.server.com
IdentityFile ~/.ssh/id_rsa.github
IdentitiesOnly yes # see NOTES below
And I add a remote git repository:
And then git commands work normally for me.
NOTES
IdentitiesOnly yes
is required to prevent the SSH default behavior of sending the identity file matching the default filename for each protocol. If you have a file named ~/.ssh/id_rsa
that will get tried BEFORE your ~/.ssh/id_rsa.github
without this option.
References
Other people’s suggestions about ~/.ssh/config
are extra complicated. It can be as simple as:
I want to pause input in a shell script, and prompt the user for choices. The standard ‘Yes, No, or Cancel’ type question. How do I accomplish this in a typical bash prompt?
The simplest and most widely available method to get user input at a shell prompt is the read
command. The best way to illustrate its use is a simple demonstration:
while true; do
read -p "Do you wish to install this program?" yn
case $yn in
[Yy]* ) make install; break;;
[Nn]* ) exit;;
* ) echo "Please answer yes or no.";;
esac
done
Another method, pointed out by Steven Huwig, is Bash’s select
command. Here is the same example using select
:
echo "Do you wish to install this program?"
select yn in "Yes" "No"; do
case $yn in
Yes ) make install; break;;
No ) exit;;
esac
done
With select
you don’t need to sanitize the input – it displays the available choices, and you type a number corresponding to your choice. It also loops automatically, so there’s no need for a while true
loop to retry if they give invalid input.
Also, Léa Gris demonstrated a way to make the request language agnostic in her answer. Adapting my first example to better serve multiple languages might look like this:
set -- $(locale LC_MESSAGES)
yesptrn="$1"; noptrn="$2"; yesword="$3"; noword="$4"
while true; do
read -p "Install (${yesword} / ${noword})? " yn
case $yn in
${yesptrn##^} ) make install; break;;
${noptrn##^} ) exit;;
* ) echo "Answer ${yesword} / ${noword}.";;
esac
done
Obviously other communication strings remain untranslated here (Install, Answer) which would need to be addressed in a more fully completed translation, but even a partial translation would be helpful in many cases.
Finally, please check out the excellent answer by F. Hauri.
Depending on
and if you want
You could use the read
command, followed by if ... then ... else
:
(Thanks to Adam Katz’s comment: Replaced the test above with one that is more portable and avoids one fork:)
But if you don’t want the user to have to hit Return, you could write:
(Edited: As @JonathanLeffler rightly suggest, saving stty’s configuration could be better than simply force them to sane.)
echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
if echo "$answer" | grep -iq "^y" ;then
echo Yes
else
echo No
fi
Note: This was tested under sh, bash, ksh, dash and busybox!
Same, but waiting explicitly for y or n:
#/bin/sh
echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo
answer=$( while ! head -c 1 | grep -i '[ny]' ;do true ;done )
stty $old_stty_cfg
if echo "$answer" | grep -iq "^y" ;then
echo Yes
else
echo No
fi
There are many tools which were built using libncurses
, libgtk
, libqt
or other graphical libraries. For example, using whiptail
:
Depending on your system, you may need to replace whiptail
with another similiar tool:
dialog --yesno "Is this a good question" 20 60 && echo Yes
gdialog --yesno "Is this a good question" 20 60 && echo Yes
kdialog --yesno "Is this a good question" 20 60 && echo Yes
where 20
is height of dialog box in number of lines and 60
is width of the dialog box. These tools all have near same syntax.
DIALOG=whiptail
if [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fi
if [ -x /usr/bin/xdialog ] ;then DIALOG=xdialog ; fi
...
$DIALOG --yesno ...
I prefer to use case
so I could even test for yes | ja | si | oui
if needed…
Under bash, we can specify the length of intended input for for the read
command:
Under bash, read
command accepts a timeout parameter, which could be useful.
read -t 3 -n 1 -p "Is this a good question (y/n)? " answer
[ -z "$answer" ] && answer="Yes" # if 'yes' have to be default choice
More sophisticated dialog boxes, beyond simple yes - no
purposes:
Progress bar:
dialog --gauge "Filling the tank" 20 60 0 < <(
for i in {1..100};do
printf "XXX\n%d\n%(%a %b %T)T progress: %d\nXXX\n" $i -1 $i
sleep .033
done
)
Little demo:
#!/bin/sh
while true ;do
[ -x "$(which ${DIALOG%% *})" ] || DIALOG=dialog
DIALOG=$($DIALOG --menu "Which tool for next run?" 20 60 12 2>&1 \
whiptail "dialog boxes from shell scripts" >/dev/tty \
dialog "dialog boxes from shell with ncurses" \
gdialog "dialog boxes from shell with Gtk" \
kdialog "dialog boxes from shell with Kde" ) || exit
clear;echo "Choosed: $DIALOG."
for i in `seq 1 100`;do
date +"`printf "XXX\n%d\n%%a %%b %%T progress: %d\nXXX\n" $i $i`"
sleep .0125
done | $DIALOG --gauge "Filling the tank" 20 60 0
$DIALOG --infobox "This is a simple info box\n\nNo action required" 20 60
sleep 3
if $DIALOG --yesno "Do you like this demo?" 20 60 ;then
AnsYesNo=Yes; else AnsYesNo=No; fi
AnsInput=$($DIALOG --inputbox "A text:" 20 60 "Text here..." 2>&1 >/dev/tty)
AnsPass=$($DIALOG --passwordbox "A secret:" 20 60 "First..." 2>&1 >/dev/tty)
$DIALOG --textbox /etc/motd 20 60
AnsCkLst=$($DIALOG --checklist "Check some..." 20 60 12 \
Correct "This demo is useful" off \
Fun "This demo is nice" off \
Strong "This demo is complex" on 2>&1 >/dev/tty)
AnsRadio=$($DIALOG --radiolist "I will:" 20 60 12 \
" -1" "Downgrade this answer" off \
" 0" "Not do anything" on \
" +1" "Upgrade this anser" off 2>&1 >/dev/tty)
out="Your answers:\nLike: $AnsYesNo\nInput: $AnsInput\nSecret: $AnsPass"
$DIALOG --msgbox "$out\nAttribs: $AnsCkLst\nNote: $AnsRadio" 20 60
done
More sample? Have a look at Using whiptail for choosing USB device and USB removable storage selector: USBKeyChooser
Example:
#!/bin/bash
set -i
HISTFILE=~/.myscript.history
history -c
history -r
myread() {
read -e -p '> ' $1
history -s ${!1}
}
trap 'history -a;exit' 0 1 2 3 6
while myread line;do
case ${line%% *} in
exit ) break ;;
* ) echo "Doing something with '$line'" ;;
esac
done
This will create a file .myscript.history
in your $HOME
directory, than you could use readline’s history commands, like Up, Down, Ctrl+r and others.
I am trying to do a simple condition check, but it doesn’t seem to work.
If $#
is equal to 0
or is greater than 1
then say hello.
I have tried the following syntax with no success:
This should work:
I’m not sure if this is different in other shells but if you wish to use <, >, you need to put them inside double parenthesis like so:
This code works for me:
I don’t think sh supports “==”. Use “=” to compare strings and -eq to compare ints.
for more details.
If you are using the bash exit code status $? as variable, it’s better to do this:
Because if you do:
The left part of the OR alters the ? < /strong > variable, sothe < strong > rightpartoftheOR < /strong > doesn′thavetheoriginal < strong>? value.
I am trying to print a text in the terminal using echo command.
I want to print the text in a red color. How can I do that?
You can use these ANSI escape codes:
Black 0;30 Dark Gray 1;30
Red 0;31 Light Red 1;31
Green 0;32 Light Green 1;32
Brown/Orange 0;33 Yellow 1;33
Blue 0;34 Light Blue 1;34
Purple 0;35 Light Purple 1;35
Cyan 0;36 Light Cyan 1;36
Light Gray 0;37 White 1;37
And then use them like this in your script:
# .---------- constant part!
# vvvv vvvv-- the code from above
RED='\033[0;31m'
NC='\033[0m' # No Color
printf "I ${RED}love${NC} Stack Overflow\n"
which prints love
in red.
From @james-lim’s comment, if you are using the echo
command, be sure to use the -e flag to allow backslash escapes.
(don’t add "\n"
when using echo unless you want to add additional empty line)
You can use the awesome tput
command (suggested in Ignacio’s answer) to produce terminal control codes for all kinds of things.
Specific tput
sub-commands are discussed later.
Call tput
as part of a sequence of commands:
Use ;
instead of &&
so if tput
errors the text still shows.
Another option is to use shell variables:
red=`tput setaf 1`
green=`tput setaf 2`
reset=`tput sgr0`
echo "${red}red text ${green}green text${reset}"
tput
produces character sequences that are interpreted by the terminal as having a special meaning. They will not be shown themselves. Note that they can still be saved into files or processed as input by programs other than the terminal.
It may be more convenient to insert tput
’s output directly into your echo
strings using command substitution:
The above command produces this on Ubuntu:
Colours are as follows:
Num Colour #define R G B
0 black COLOR_BLACK 0,0,0
1 red COLOR_RED 1,0,0
2 green COLOR_GREEN 0,1,0
3 yellow COLOR_YELLOW 1,1,0
4 blue COLOR_BLUE 0,0,1
5 magenta COLOR_MAGENTA 1,0,1
6 cyan COLOR_CYAN 0,1,1
7 white COLOR_WHITE 1,1,1
There are also non-ANSI versions of the colour setting functions (setb
instead of setab
, and setf
instead of setaf
) which use different numbers, not given here.
tput bold # Select bold mode
tput dim # Select dim (half-bright) mode
tput smul # Enable underline mode
tput rmul # Disable underline mode
tput rev # Turn on reverse video mode
tput smso # Enter standout (bold) mode
tput rmso # Exit standout mode
tput cup Y X # Move cursor to screen postion X,Y (top left is 0,0)
tput cuf N # Move N characters forward (right)
tput cub N # Move N characters back (left)
tput cuu N # Move N lines up
tput ll # Move to last line, first column (if no cup)
tput sc # Save the cursor position
tput rc # Restore the cursor position
tput lines # Output the number of lines of the terminal
tput cols # Output the number of columns of the terminal
tput ech N # Erase N characters
tput clear # Clear screen and move the cursor to 0,0
tput el 1 # Clear to beginning of line
tput el # Clear to end of line
tput ed # Clear to end of screen
tput ich N # Insert N characters (moves rest of line forward!)
tput il N # Insert N lines
With compiz wobbly windows, the bel
command makes the terminal wobble for a second to draw the user’s attention.
tput
accepts scripts containing one command per line, which are executed in order before tput
exits.
Avoid temporary files by echoing a multiline string and piping it:
man 1 tput
man 5 terminfo
for the complete list of commands and more details on these options. (The corresponding tput
command is listed in the Cap-name
column of the huge table that starts at line 81.)
I have two shell scripts, a.sh
and b.sh
.
How can I call b.sh
from within the shell script a.sh
?
There are a couple of different ways you can do this:
Make the other script executable, add the #!/bin/bash
line at the top, and the path where the file is to the $PATH environment variable. Then you can call it as a normal command;
Or call it with the source
command (alias is .
) like this: source /path/to/script
;
Or use the bash
command to execute it: /bin/bash /path/to/script
;
The first and third methods execute the script as another process, so variables and functions in the other script will not be accessible.
The second method executes the script in the first script’s process, and pulls in variables and functions from the other script so they are usable from the calling script.
In the second method, if you are using exit
in second script, it will exit the first script as well. Which will not happen in first and third methods.
Check this out.
#!/bin/bash
echo "This script is about to run another script."
sh ./script.sh
echo "This script has just run another script."
If I make changes to .bashrc
, how do I reload it without logging out and back in?
You just have to enter the command:
or you can use the shorter version of the command:
or you could use;
does the same thing. (and easier to remember, at least for me)
exec command replaces the shell with given program, in our example, it replaces our shell with bash (with the updated configuration files)
To complement and contrast the two most popular answers, . ~/.bashrc
and exec bash
:
Both solutions effectively reload ~/.bashrc
, but there are differences:
. ~/.bashrc
or source ~/.bashrc
will preserve your current shell:
~/.bashrc
into the current shell (sourcing) makes, the current shell and its state are preserved, which includes environment variables, shell variables, shell options, shell functions, and command history.
exec bash
, or, more robustly, exec "$BASH"
[1], will replace your current shell with a new instance, and therefore only preserve your current shell’s environment variables (including ones you’ve defined ad-hoc).
Depending on your needs, one or the other approach may be preferred.
[1] exec bash
could in theory execute a different bash
executable than the one that started the current shell, if it happens to exist in a directory listed earlier in the $PATH
. Since special variable $BASH
always contains the full path of the executable that started the current shell, exec "$BASH"
is guaranteed to use the same executable.
A note re "..."
around $BASH
: double-quoting ensures that the variable value is used as-is, without interpretation by Bash; if the value has no embedded spaces or other shell metacharacters (which is not likely in this case), you don’t strictly need double quotes, but using them is a good habit to form.
I’m trying to parse JSON returned from a curl request, like so:
curl 'http://twitter.com/users/username.json' |
sed -e 's/[{}]/''/g' |
awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'
The above splits the JSON into fields, for example:
% ...
"geo_enabled":false
"friends_count":245
"profile_text_color":"000000"
"status":"in_reply_to_screen_name":null
"source":"web"
"truncated":false
"text":"My status"
"favorited":false
% ...
How do I print a specific field (denoted by the -v k=text
)?
There are a number of tools specifically designed for the purpose of manipulating JSON from the command line, and will be a lot easier and more reliable than doing it with Awk, such as jq
:
You can also do this with tools that are likely already installed on your system, like Python using the json
module, and so avoid any extra dependencies, while still having the benefit of a proper JSON parser. The following assume you want to use UTF-8, which the original JSON should be encoded in and is what most modern terminals use as well:
Python 2:
export PYTHONIOENCODING=utf8
curl -s 'https://api.github.com/users/lambda' | \
python -c "import sys, json; print json.load(sys.stdin)['name']"
Python 3:
curl -s 'https://api.github.com/users/lambda' | \
python3 -c "import sys, json; print(json.load(sys.stdin)['name'])"
This answer originally recommended jsawk, which should still work, but is a little more cumbersome to use than jq
, and depends on a standalone JavaScript interpreter being installed which is less common than a Python interpreter, so the above answers are probably preferable:
This answer also originally used the Twitter API from the question, but that API no longer works, making it hard to copy the examples to test out, and the new Twitter API requires API keys, so I’ve switched to using the GitHub API which can be used easily without API keys. The first answer for the original question would be:
To quickly extract the values for a particular key, I personally like to use “grep -o”, which only returns the regex’s match. For example, to get the “text” field from tweets, something like:
This regex is more robust than you might think; for example, it deals fine with strings having embedded commas and escaped quotes inside them. I think with a little more work you could make one that is actually guaranteed to extract the value, if it’s atomic. (If it has nesting, then a regex can’t do it of course.)
And to further clean (albeit keeping the string’s original escaping) you can use something like: | perl -pe 's/"text"://; s/^"//; s/",$//'
. (I did this for this analysis.)
To all the haters who insist you should use a real JSON parser – yes, that is essential for correctness, but
grep -o
is orders of magnitude faster than the Python standard json
library, at least when doing this for tweets (which are ~2 KB each). I’m not sure if this is just because json
is slow (I should compare to yajl sometime); but in principle, a regex should be faster since it’s finite state and much more optimizable, instead of a parser that has to support recursion, and in this case, spends lots of CPU building trees for structures you don’t care about. (If someone wrote a finite state transducer that did proper (depth-limited) JSON parsing, that would be fantastic! In the meantime we have “grep -o”.)
To write maintainable code, I always use a real parsing library. I haven’t tried jsawk, but if it works well, that would address point #1.
One last, wackier, solution: I wrote a script that uses Python json
and extracts the keys you want, into tab-separated columns; then I pipe through a wrapper around awk
that allows named access to columns. In here: the json2tsv and tsvawk scripts. So for this example it would be:
This approach doesn’t address #2, is more inefficient than a single Python script, and it’s a little brittle: it forces normalization of newlines and tabs in string values, to play nice with awk’s field/record-delimited view of the world. But it does let you stay on the command line, with more correctness than grep -o
.
On the basis that some of the recommendations here (esp in the comments) suggested the use of Python, I was disappointed not to find an example.
So, here’s a one liner to get a single value from some JSON data. It assumes that you are piping the data in (from somewhere) and so should be useful in a scripting context.
I have this bash script and I had a problem in line 16. How can I take the previous result of line 15 and add it to the variable in line 16?
For integers:
Use arithmetic expansion: $((EXPR))
sh num=$((num1 + num2)) num=$(($num1 + $num2)) # also works num=$((num1 + 2 + 3)) # ... num=$[num1+num2] # old, deprecated arithmetic expression syntax
Using the external expr
utility. Note that this is only needed for really old systems.
sh num=`expr $num1 + $num2` # whitespace for expr is important
For floating point:
Bash doesn’t directly support this, but there’s a couple of external tools you can use:
num=$(awk "BEGIN {print $num1+$num2; exit}")
num=$(python -c "print $num1+$num2")
num=$(perl -e "print $num1+$num2")
num=$(echo $num1 + $num2 | bc) # whitespace for echo is important
You can also use scientific notation (e.g.: 2.5e+2
)
Common pitfalls:
When setting a variable, you cannot have whitespace on either side of =
, otherwise it will force the shell to interpret the first word as the name of the application to run (eg: num=
or num
)
num= 1
num =2
bc
and expr
expect each number and operator as a separate argument, so whitespace is important. They cannot process arguments like 3+
+4
.
num=
expr $num1+ $num2``Use the $(( ))
arithmetic expansion.
See http://tldp.org/LDP/abs/html/arithexp.html for more information.
I know it, forget it and relearn it again. Time to write it down.
To run a non-executable sh
script, use:
To run a non-executable bash
script, use:
To start an executable (which is any file with executable permission); you just specify it by its path:
To make a script executable, give it the necessary permission:
When a file is executable, the kernel is responsible for figuring out how to execte it. For non-binaries, this is done by looking at the first line of the file. It should contain a hashbang
:
The hashbang tells the kernel what program to run (in this case the command /usr/bin/env
is ran with the argument bash
). Then, the script is passed to the program (as second argument) along with all the arguments you gave the script as subsequent arguments.
That means every script that is executable should have a hashbang. If it doesn’t, you’re not telling the kernel what it is, and therefore the kernel doesn’t know what program to use to interprete it. It could be bash
, perl
, python
, sh
, or something else. (In reality, the kernel will often use the user’s default shell to interprete the file, which is very dangerous because it might not be the right interpreter at all or it might be able to parse some of it but with subtle behavioural differences such as is the case between sh
and bash
).
/usr/bin/env
Most commonly, you’ll see hash bangs like so:
The result is that the kernel will run the program /bin/bash
to interpret the script. Unfortunately, bash
is not always shipped by default, and it is not always available in /bin
. While on Linux machines it usually is, there are a range of other POSIX machines where bash
ships in various locations, such as /usr/xpg/bin/bash
or /usr/local/bin/bash
.
To write a portable bash script, we can therefore not rely on hard-coding the location of the bash
program. POSIX already has a mechanism for dealing with that: PATH
. The idea is that you install your programs in one of the directories that are in PATH
and the system should be able to find your program when you want to run it by name.
Sadly, you cannot just do this:
The kernel won’t (some might) do a PATH
search for you. There is a program that can do a PATH
search for you, though, it’s called env
. Luckily, nearly all systems have an env
program installed in /usr/bin
. So we start env
using a hardcoded path, which then does a PATH
search for bash
and runs it so that it can interpret your script:
This approach has one downside: According to POSIX, the hashbang can have one argument. In this case, we use bash
as the argument to the env
program. That means we have no space left to pass arguments to bash
. So there’s no way to convert something like #!/bin/bash -exu
to this scheme. You’ll have to put set -exu
after the hashbang instead.
This approach also has another advantage: Some systems may ship with a /bin/bash
, but the user may not like it, may find it’s buggy or outdated, and may have installed his own bash
somewhere else. This is often the case on OS X (Macs) where Apple ships an outdated /bin/bash
and users install an up-to-date /usr/local/bin/bash
using something like Homebrew. When you use the env
approach which does a PATH
search, you take the user’s preference into account and use his preferred bash over the one his system shipped with.
To start the shell-script ‘file.sh’:
Another option is set executable permission using chmod command:
Now run .sh file as follows:
For the bourne shell:
For bash:
I tried to declare a boolean variable in a shell script using the following syntax:
Is this correct? Also, if I wanted to update that variable would I use the same syntax? Finally, is the following syntax for using boolean variables as expressions correct:
Revised Answer (Feb 12, 2014)
the_world_is_flat=true
# ...do something interesting...
if [ "$the_world_is_flat" = true ] ; then
echo 'Be careful not to fall off!'
fi
Original Answer
Caveats: https://stackoverflow.com/a/21210966/89391
the_world_is_flat=true
# ...do something interesting...
if $the_world_is_flat ; then
echo 'Be careful not to fall off!'
fi
From: Using boolean variables in Bash
The reason the original answer is included here is because the comments before the revision on Feb 12, 2014 pertain only to the original answer, and many of the comments are wrong when associated with the revised answer. For example, Dennis Williamson’s comment about bash builtin true
on Jun 2, 2010 only applies to the original answer, not the revised.
I do not recommend the accepted answer1. Its syntax is pretty but it has some flaws.
Say we have the following condition.
In the following cases2, this condition will evaluate to true and execute the nested command.
# Variable var not defined beforehand. Case 1
var='' # Equivalent to var="". Case 2
var= # Case 3
unset var # Case 4
var='<some valid command>' # Case 5
Typically you only want your condition to evaluate to true when your “boolean” variable, var
in this example, is explicitly set to true. All the others cases are dangerously misleading!
The last case (#5) is especially naughty because it will execute the command contained in the variable (which is why the condition evaluates to true for valid commands3, 4).
Here is a harmless example:
var='echo this text will be displayed when the condition is evaluated'
if $var; then
echo 'Muahahaha!'
fi
# Outputs:
# this text will be displayed when the condition is evaluated
# Muahahaha!
Quoting your variables is safer, e.g. if "$var"; then
. In the above cases, you should get a warning that the command is not found. But we can still do better (see my recommendations at the bottom).
Also see Mike Holt’s explanation of Miku’s original answer.
This approach also has unexpected behaviour.
var=false
if [ $var ]; then
echo "This won't print, var is false!"
fi
# Outputs:
# This won't print, var is false!
You would expect the above condition to evaluate to false, thus never executing the nested statement. Surprise!
Quoting the value ("false"
), quoting the variable ("$var"
), or using test
or [[
instead of [
, do not make a difference.
Here are ways I recommend you check your “booleans”. They work as expected.
bool=true
if [ "$bool" = true ]; then
if [ "$bool" = "true" ]; then
if [[ "$bool" = true ]]; then
if [[ "$bool" = "true" ]]; then
if [[ "$bool" == true ]]; then
if [[ "$bool" == "true" ]]; then
if test "$bool" = true; then
if test "$bool" = "true"; then
They’re all pretty much equivalent. You’ll have to type a few more keystrokes than the approaches in the other answers5 but your code will be more defensive.
man woman
would still be considered a valid command, even if no such man page exists.
There seems to be some misunderstanding here about the Bash builtin true
, and more specifically, about how Bash expands and interprets expressions inside brackets.
The code in miku’s answer has absolutely nothing to do with the Bash builtin true
, nor /bin/true
, nor any other flavor of the true
command. In this case, true
is nothing more than a simple character string, and no call to the true
command/builtin is ever made, neither by the variable assignment, nor by the evaluation of the conditional expression.
The following code is functionally identical to the code in the miku’s answer:
the_world_is_flat=yeah
if [ "$the_world_is_flat" = yeah ]; then
echo 'Be careful not to fall off!'
fi
The only difference here is that the four characters being compared are ‘y’, ‘e’, ‘a’, and ‘h’ instead of ‘t’, ‘r’, ‘u’, and ‘e’. That’s it. There’s no attempt made to call a command or builtin named yeah
, nor is there (in miku’s example) any sort of special handling going on when Bash parses the token true
. It’s just a string, and a completely arbitrary one at that.
Update (2014-02-19): After following the link in miku’s answer, now I see where some of the confusion is coming from. Miku’s answer uses single brackets, but the code snippet he links to does not use brackets. It’s just:
Both code snippets will behave the same way, but the brackets completely change what’s going on under the hood.
Here’s what Bash is doing in each case:
No brackets:
$the_world_is_flat
to the string "true"
.
"true"
as a command.
true
command (either a builtin or /bin/true
, depending on the Bash version).
true
command (which is always 0) with 0. Recall that in most shells, an exit code of 0 indicates success and anything else indicates failure.
if
statement’s then
clause
Brackets:
$the_world_is_flat
to the string "true"
.
string1 = string2
. The =
operator is bash’s string comparison operator. So…
"true"
and "true"
.
if
statement’s then
clause.
The no-brackets code works, because the true
command returns an exit code of 0, which indicates success. The bracketed code works, because the value of $the_world_is_flat
is identical to the string literal true
on the right side of the =
.
Just to drive the point home, consider the following two snippets of code:
This code (if run with root privileges) will reboot your computer:
This code just prints “Nice try.” The reboot command is not called.
Update (2014-04-14) To answer the question in the comments regarding the difference between =
and ==
: AFAIK, there is no difference. The ==
operator is a Bash-specific synonym for =
, and as far as I’ve seen, they work exactly the same in all contexts.
Note, however, that I’m specifically talking about the =
and ==
string comparison operators used in either [ ]
or [[ ]]
tests. I’m not suggesting that =
and ==
are interchangeable everywhere in bash.
For example, you obviously can’t do variable assignment with ==
, such as var=="foo"
(well technically you can do this, but the value of var
will be "=foo"
, because Bash isn’t seeing an ==
operator here, it’s seeing an =
(assignment) operator, followed by the literal value ="foo"
, which just becomes "=foo"
).
Also, although =
and ==
are interchangeable, you should keep in mind that how those tests work does depend on whether you’re using it inside [ ]
or [[ ]]
, and also on whether or not the operands are quoted. You can read more about that in Advanced Bash Scripting Guide: 7.3 Other Comparison Operators (scroll down to the discussion of =
and ==
).
Is there a way in bash to convert a string into a lower case string?
For example, if I have:
I want to convert it to:
The are various ways:
You may run into portability issues with the following examples:
$ echo "$a" | sed -e 's/\(.*\)/\L\1/'
hi all
# this also works:
$ sed -e 's/\(.*\)/\L\1/' <<< "$a"
hi all
Note: YMMV on this one. Doesn’t work for me (GNU bash version 4.2.46 and 4.0.33 (and same behaviour 2.05b.0 but nocasematch is not implemented)) even with using shopt -u nocasematch;
. Unsetting that nocasematch causes [[ “fooBaR” == “FOObar” ]] to match OK BUT inside case weirdly [b-z] are incorrectly matched by [A-Z]. Bash is confused by the double-negative (“unsetting nocasematch”)! :-)
In Bash 4:
To lowercase
$ 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)
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"
A few words
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.
Edit: Corrected tilde behavior to match Bash 4.3.
I needed to write a script to enter multi-line input to a program (psql
).
After a bit of googling, I found the following syntax works:
cat << EOF | psql ---params
BEGIN;
`pg_dump ----something`
update table .... statement ...;
END;
EOF
This correctly constructs the multi-line string (from BEGIN;
to END;
, inclusive) and pipes it as an input to psql
.
But I have no idea how/why it works, can some one please explain?
I’m referring mainly to cat << EOF
, I know >
outputs to a file, >>
appends to a file, <
reads input from file.
What does <<
exactly do?
And is there a man page for it?
This is called heredoc format to provide a string into stdin. See https://en.wikipedia.org/wiki/Here_document#Unix_shells for more details.
From man bash
:
Here Documents
This type of redirection instructs the shell to read input from the current source until a line containing only word (with no trailing blanks) is seen.
All of the lines read up to that point are then used as the standard input for a command.
The format of here-documents is:
No parameter expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. In the latter case, the character sequence
\<newline>
is ignored, and\
must be used to quote the characters\
,$
, and ```.If the redirection operator is
<<-
, then all leading tab characters are stripped from input lines and the line containing delimiter. This allows here-documents within shell scripts to be indented in a natural fashion.
The cat <<EOF
syntax is very useful when working with multi-line text in Bash, eg. when assigning multi-line string to a shell variable, file or a pipe.
cat <<EOF
syntax usage in Bash:
The $sql
variable now holds the new-line characters too. You can verify with echo -e "$sql"
.
The print.sh
file now contains:
The b.txt
file contains bar
and baz
lines. The same output is printed to stdout
.
In your case, “EOF” is known as a “Here Tag”. Basically <<Here
tells the shell that you are going to enter a multiline string until the “tag” Here
. You can name this tag as you want, it’s often EOF
or STOP
.
Some rules about the Here tags:
example:
$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
> HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string
I have only found how to wait for user input. However, I only want to pause so that my while true
doesn’t crash my computer.
I tried pause(1)
, but it says -bash: syntax error near unexpected token '1'
. How can it be done?
Use the sleep
command.
Example:
sleep .5 # Waits 0.5 second.
sleep 5 # Waits 5 seconds.
sleep 5s # Waits 5 seconds.
sleep 5m # Waits 5 minutes.
sleep 5h # Waits 5 hours.
sleep 5d # Waits 5 days.
One can also employ decimals when specifying a time unit; e.g. sleep 1.5s
And what about:
In a Bash script I would like to split a line into pieces and store them in an array.
The line:
I would like to have them in an array like this:
I would like to use simple code, the command’s speed doesn’t matter. How can I do it?
Note that the characters in $IFS
are treated individually as separators so that in this case fields may be separated by either a comma or a space rather than the sequence of the two characters. Interestingly though, empty fields aren’t created when comma-space appears in the input because the space is treated specially.
To access an individual element:
To iterate over the elements:
To get both the index and the value:
The last example is useful because Bash arrays are sparse. In other words, you can delete an element or add an element and then the indices are not contiguous.
To get the number of elements in an array:
As mentioned above, arrays can be sparse so you shouldn’t use the length to get the last element. Here’s how you can in Bash 4.2 and later:
in any version of Bash (from somewhere after 2.05b):
Larger negative offsets select farther from the end of the array. Note the space before the minus sign in the older form. It is required.
All of the answers to this question are wrong in one way or another.
1: This is a misuse of $IFS
. The value of the $IFS
variable is not taken as a single variable-length string separator, rather it is taken as a set of single-character string separators, where each field that read
splits off from the input line can be terminated by any character in the set (comma or space, in this example).
Actually, for the real sticklers out there, the full meaning of $IFS
is slightly more involved. From the bash manual:
The shell treats each character of IFS as a delimiter, and splits the results of the other expansions into words using these characters as field terminators. If IFS is unset, or its value is exactly <space><tab><newline>, the default, then sequences of <space>, <tab>, and <newline> at the beginning and end of the results of the previous expansions are ignored, and any sequence of IFS characters not at the beginning or end serves to delimit words. If IFS has a value other than the default, then sequences of the whitespace characters <space>, <tab>, and <newline> are ignored at the beginning and end of the word, as long as the whitespace character is in the value of IFS (an IFS whitespace character). Any character in IFS that is not IFS whitespace, along with any adjacent IFS whitespace characters, delimits a field. A sequence of IFS whitespace characters is also treated as a delimiter. If the value of IFS is null, no word splitting occurs.
Basically, for non-default non-null values of $IFS
, fields can be separated with either (1) a sequence of one or more characters that are all from the set of “IFS whitespace characters” (that is, whichever of <space>, <tab>, and <newline> (“newline” meaning line feed (LF)) are present anywhere in $IFS
), or (2) any non-“IFS whitespace character” that’s present in $IFS
along with whatever “IFS whitespace characters” surround it in the input line.
For the OP, it’s possible that the second separation mode I described in the previous paragraph is exactly what he wants for his input string, but we can be pretty confident that the first separation mode I described is not correct at all. For example, what if his input string was 'Los Angeles, United States, North America'
?
IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")
2: Even if you were to use this solution with a single-character separator (such as a comma by itself, that is, with no following space or other baggage), if the value of the $string
variable happens to contain any LFs, then read
will stop processing once it encounters the first LF. The read
builtin only processes one line per invocation. This is true even if you are piping or redirecting input only to the read
statement, as we are doing in this example with the here-string mechanism, and thus unprocessed input is guaranteed to be lost. The code that powers the read
builtin has no knowledge of the data flow within its containing command structure.
You could argue that this is unlikely to cause a problem, but still, it’s a subtle hazard that should be avoided if possible. It is caused by the fact that the read
builtin actually does two levels of input splitting: first into lines, then into fields. Since the OP only wants one level of splitting, this usage of the read
builtin is not appropriate, and we should avoid it.
3: A non-obvious potential issue with this solution is that read
always drops the trailing field if it is empty, although it preserves empty fields otherwise. Here’s a demo:
string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")
Maybe the OP wouldn’t care about this, but it’s still a limitation worth knowing about. It reduces the robustness and generality of the solution.
This problem can be solved by appending a dummy trailing delimiter to the input string just prior to feeding it to read
, as I will demonstrate later.
(Note: I added the missing parentheses around the command substitution which the answerer seems to have omitted.)
These solutions leverage word splitting in an array assignment to split the string into fields. Funnily enough, just like read
, general word splitting also uses the $IFS
special variable, although in this case it is implied that it is set to its default value of <space><tab><newline>, and therefore any sequence of one or more IFS characters (which are all whitespace characters now) is considered to be a field delimiter.
This solves the problem of two levels of splitting committed by read
, since word splitting by itself constitutes only one level of splitting. But just as before, the problem here is that the individual fields in the input string can already contain $IFS
characters, and thus they would be improperly split during the word splitting operation. This happens to not be the case for any of the sample input strings provided by these answerers (how convenient…), but of course that doesn’t change the fact that any code base that used this idiom would then run the risk of blowing up if this assumption were ever violated at some point down the line. Once again, consider my counterexample of 'Los Angeles, United States, North America'
(or 'Los Angeles:United States:North America'
).
Also, word splitting is normally followed by filename expansion (aka pathname expansion aka globbing), which, if done, would potentially corrupt words containing the characters *
, ?
, or [
followed by ]
(and, if extglob
is set, parenthesized fragments preceded by ?
, *
, +
, @
, or !
) by matching them against file system objects and expanding the words (“globs”) accordingly. The first of these three answerers has cleverly undercut this problem by running set -f
beforehand to disable globbing. Technically this works (although you should probably add set +f
afterward to reenable globbing for subsequent code which may depend on it), but it’s undesirable to have to mess with global shell settings in order to hack a basic string-to-array parsing operation in local code.
Another issue with this answer is that all empty fields will be lost. This may or may not be a problem, depending on the application.
Note: If you’re going to use this solution, it’s better to use the ${string//:/ }
“pattern substitution” form of parameter expansion, rather than going to the trouble of invoking a command substitution (which forks the shell), starting up a pipeline, and running an external executable (tr
or sed
), since parameter expansion is purely a shell-internal operation. (Also, for the tr
and sed
solutions, the input variable should be double-quoted inside the command substitution; otherwise word splitting would take effect in the echo
command and potentially mess with the field values. Also, the $(...)
form of command substitution is preferable to the old ...
form since it simplifies nesting of command substitutions and allows for better syntax highlighting by text editors.)
str="a, b, c, d" # assuming there is a space after ',' as in Q
arr=(${str//,/}) # delete all occurrences of ','
This answer is almost the same as #2. The difference is that the answerer has made the assumption that the fields are delimited by two characters, one of which being represented in the default $IFS
, and the other not. He has solved this rather specific case by removing the non-IFS-represented character using a pattern substitution expansion and then using word splitting to split the fields on the surviving IFS-represented delimiter character.
This is not a very generic solution. Furthermore, it can be argued that the comma is really the “primary” delimiter character here, and that stripping it and then depending on the space character for field splitting is simply wrong. Once again, consider my counterexample: 'Los Angeles, United States, North America'
.
Also, again, filename expansion could corrupt the expanded words, but this can be prevented by temporarily disabling globbing for the assignment with set -f
and then set +f
.
Also, again, all empty fields will be lost, which may or may not be a problem depending on the application.
string='first line
second line
third line'
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"
This is similar to #2 and #3 in that it uses word splitting to get the job done, only now the code explicitly sets $IFS
to contain only the single-character field delimiter present in the input string. It should be repeated that this cannot work for multicharacter field delimiters such as the OP’s comma-space delimiter. But for a single-character delimiter like the LF used in this example, it actually comes close to being perfect. The fields cannot be unintentionally split in the middle as we saw with previous wrong answers, and there is only one level of splitting, as required.
One problem is that filename expansion will corrupt affected words as described earlier, although once again this can be solved by wrapping the critical statement in set -f
and set +f
.
Another potential problem is that, since LF qualifies as an “IFS whitespace character” as defined earlier, all empty fields will be lost, just as in #2 and #3. This would of course not be a problem if the delimiter happens to be a non-“IFS whitespace character”, and depending on the application it may not matter anyway, but it does vitiate the generality of the solution.
So, to sum up, assuming you have a one-character delimiter, and it is either a non-“IFS whitespace character” or you don’t care about empty fields, and you wrap the critical statement in set -f
and set +f
, then this solution works, but otherwise not.
(Also, for information’s sake, assigning a LF to a variable in bash can be done more easily with the $'...'
syntax, e.g. IFS=$'\n';
.)
This solution is effectively a cross between #1 (in that it sets $IFS
to comma-space) and #2-4 (in that it uses word splitting to split the string into fields). Because of this, it suffers from most of the problems that afflict all of the above wrong answers, sort of like the worst of all worlds.
Also, regarding the second variant, it may seem like the eval
call is completely unnecessary, since its argument is a single-quoted string literal, and therefore is statically known. But there’s actually a very non-obvious benefit to using eval
in this way. Normally, when you run a simple command which consists of a variable assignment only, meaning without an actual command word following it, the assignment takes effect in the shell environment:
This is true even if the simple command involves multiple variable assignments; again, as long as there’s no command word, all variable assignments affect the shell environment:
But, if the variable assignment is attached to a command name (I like to call this a “prefix assignment”) then it does not affect the shell environment, and instead only affects the environment of the executed command, regardless whether it is a builtin or external:
IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it
Relevant quote from the bash manual:
If no command name results, the variable assignments affect the current shell environment. Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment.
It is possible to exploit this feature of variable assignment to change $IFS
only temporarily, which allows us to avoid the whole save-and-restore gambit like that which is being done with the $OIFS
variable in the first variant. But the challenge we face here is that the command we need to run is itself a mere variable assignment, and hence it would not involve a command word to make the $IFS
assignment temporary. You might think to yourself, well why not just add a no-op command word to the statement like the : builtin
to make the $IFS
assignment temporary? This does not work because it would then make the $array
assignment temporary as well:
So, we’re effectively at an impasse, a bit of a catch-22. But, when eval
runs its code, it runs it in the shell environment, as if it was normal, static source code, and therefore we can run the $array
assignment inside the eval
argument to have it take effect in the shell environment, while the $IFS
prefix assignment that is prefixed to the eval
command will not outlive the eval
command. This is exactly the trick that is being used in the second variant of this solution:
So, as you can see, it’s actually quite a clever trick, and accomplishes exactly what is required (at least with respect to assignment effectation) in a rather non-obvious way. I’m actually not against this trick in general, despite the involvement of eval
; just be careful to single-quote the argument string to guard against security threats.
But again, because of the “worst of all worlds” agglomeration of problems, this is still a wrong answer to the OP’s requirement.
Um… what? The OP has a string variable that needs to be parsed into an array. This “answer” starts with the verbatim contents of the input string pasted into an array literal. I guess that’s one way to do it.
It looks like the answerer may have assumed that the $IFS
variable affects all bash parsing in all contexts, which is not true. From the bash manual:
IFS The Internal Field Separator that is used for word splitting after expansion and to split lines into words with the read builtin command. The default value is <space><tab><newline>.
So the $IFS
special variable is actually only used in two contexts: (1) word splitting that is performed after expansion (meaning not when parsing bash source code) and (2) for splitting input lines into words by the read
builtin.
Let me try to make this clearer. I think it might be good to draw a distinction between parsing and execution. Bash must first parse the source code, which obviously is a parsing event, and then later it executes the code, which is when expansion comes into the picture. Expansion is really an execution event. Furthermore, I take issue with the description of the $IFS
variable that I just quoted above; rather than saying that word splitting is performed after expansion, I would say that word splitting is performed during expansion, or, perhaps even more precisely, word splitting is part of the expansion process. The phrase “word splitting” refers only to this step of expansion; it should never be used to refer to the parsing of bash source code, although unfortunately the docs do seem to throw around the words “split” and “words” a lot. Here’s a relevant excerpt from the linux.die.net version of the bash manual:
Expansion is performed on the command line after it has been split into words. There are seven kinds of expansion performed: brace expansion, tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, word splitting, and pathname expansion.
The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion.
You could argue the GNU version of the manual does slightly better, since it opts for the word “tokens” instead of “words” in the first sentence of the Expansion section:
Expansion is performed on the command line after it has been split into tokens.
The important point is, $IFS
does not change the way bash parses source code. Parsing of bash source code is actually a very complex process that involves recognition of the various elements of shell grammar, such as command sequences, command lists, pipelines, parameter expansions, arithmetic substitutions, and command substitutions. For the most part, the bash parsing process cannot be altered by user-level actions like variable assignments (actually, there are some minor exceptions to this rule; for example, see the various compatxx
shell settings, which can change certain aspects of parsing behavior on-the-fly). The upstream “words”/“tokens” that result from this complex parsing process are then expanded according to the general process of “expansion” as broken down in the above documentation excerpts, where word splitting of the expanded (expanding?) text into downstream words is simply one step of that process. Word splitting only touches text that has been spit out of a preceding expansion step; it does not affect literal text that was parsed right off the source bytestream.
string='first line
second line
third line'
while read -r line; do lines+=("$line"); done <<<"$string"
This is one of the best solutions. Notice that we’re back to using read
. Didn’t I say earlier that read
is inappropriate because it performs two levels of splitting, when we only need one? The trick here is that you can call read
in such a way that it effectively only does one level of splitting, specifically by splitting off only one field per invocation, which necessitates the cost of having to call it repeatedly in a loop. It’s a bit of a sleight of hand, but it works.
But there are problems. First: When you provide at least one NAME argument to read
, it automatically ignores leading and trailing whitespace in each field that is split off from the input string. This occurs whether $IFS
is set to its default value or not, as described earlier in this post. Now, the OP may not care about this for his specific use-case, and in fact, it may be a desirable feature of the parsing behavior. But not everyone who wants to parse a string into fields will want this. There is a solution, however: A somewhat non-obvious usage of read
is to pass zero NAME arguments. In this case, read
will store the entire input line that it gets from the input stream in a variable named $REPLY
, and, as a bonus, it does not strip leading and trailing whitespace from the value. This is a very robust usage of read
which I’ve exploited frequently in my shell programming career. Here’s a demonstration of the difference in behavior:
string=$' a b \n c d \n e f '; ## input string
a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a b" [1]="c d" [2]="e f") ## read trimmed surrounding whitespace
a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]=" a b " [1]=" c d " [2]=" e f ") ## no trimming
The second issue with this solution is that it does not actually address the case of a custom field separator, such as the OP’s comma-space. As before, multicharacter separators are not supported, which is an unfortunate limitation of this solution. We could try to at least split on comma by specifying the separator to the -d
option, but look what happens:
string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")
Predictably, the unaccounted surrounding whitespace got pulled into the field values, and hence this would have to be corrected subsequently through trimming operations (this could also be done directly in the while-loop). But there’s another obvious error: Europe is missing! What happened to it? The answer is that read
returns a failing return code if it hits end-of-file (in this case we can call it end-of-string) without encountering a final field terminator on the final field. This causes the while-loop to break prematurely and we lose the final field.
Technically this same error afflicted the previous examples as well; the difference there is that the field separator was taken to be LF, which is the default when you don’t specify the -d
option, and the <<<
(“here-string”) mechanism automatically appends a LF to the string just before it feeds it as input to the command. Hence, in those cases, we sort of accidentally solved the problem of a dropped final field by unwittingly appending an additional dummy terminator to the input. Let’s call this solution the “dummy-terminator” solution. We can apply the dummy-terminator solution manually for any custom delimiter by concatenating it against the input string ourselves when instantiating it in the here-string:
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
There, problem solved. Another solution is to only break the while-loop if both (1) read
returned failure and (2) $REPLY
is empty, meaning read
was not able to read any characters prior to hitting end-of-file. Demo:
a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
This approach also reveals the secretive LF that automatically gets appended to the here-string by the <<<
redirection operator. It could of course be stripped off separately through an explicit trimming operation as described a moment ago, but obviously the manual dummy-terminator approach solves it directly, so we could just go with that. The manual dummy-terminator solution is actually quite convenient in that it solves both of these two problems (the dropped-final-field problem and the appended-LF problem) in one go.
So, overall, this is quite a powerful solution. It’s only remaining weakness is a lack of support for multicharacter delimiters, which I will address later.
(This is actually from the same post as #7; the answerer provided two solutions in the same post.)
The readarray
builtin, which is a synonym for mapfile
, is ideal. It’s a builtin command which parses a bytestream into an array variable in one shot; no messing with loops, conditionals, substitutions, or anything else. And it doesn’t surreptitiously strip any whitespace from the input string. And (if -O
is not given) it conveniently clears the target array before assigning to it. But it’s still not perfect, hence my criticism of it as a “wrong answer”.
First, just to get this out of the way, note that, just like the behavior of read
when doing field-parsing, readarray
drops the trailing field if it is empty. Again, this is probably not a concern for the OP, but it could be for some use-cases. I’ll come back to this in a moment.
Second, as before, it does not support multicharacter delimiters. I’ll give a fix for this in a moment as well.
Third, the solution as written does not parse the OP’s input string, and in fact, it cannot be used as-is to parse it. I’ll expand on this momentarily as well.
For the above reasons, I still consider this to be a “wrong answer” to the OP’s question. Below I’ll give what I consider to be the right answer.
Right answer
Here’s a naïve attempt to make #8 work by just specifying the -d
option:
string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
We see the result is identical to the result we got from the double-conditional approach of the looping read
solution discussed in #7. We can almost solve this with the manual dummy-terminator trick:
readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')
The problem here is that readarray
preserved the trailing field, since the <<<
redirection operator appended the LF to the input string, and therefore the trailing field was not empty (otherwise it would’ve been dropped). We can take care of this by explicitly unsetting the final array element after-the-fact:
readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
The only two problems that remain, which are actually related, are (1) the extraneous whitespace that needs to be trimmed, and (2) the lack of support for multicharacter delimiters.
The whitespace could of course be trimmed afterward (for example, see How to trim whitespace from a Bash variable?). But if we can hack a multicharacter delimiter, then that would solve both problems in one shot.
Unfortunately, there’s no direct way to get a multicharacter delimiter to work. The best solution I’ve thought of is to preprocess the input string to replace the multicharacter delimiter with a single-character delimiter that will be guaranteed not to collide with the contents of the input string. The only character that has this guarantee is the NUL byte. This is because, in bash (though not in zsh, incidentally), variables cannot contain the NUL byte. This preprocessing step can be done inline in a process substitution. Here’s how to do it using awk:
readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
There, finally! This solution will not erroneously split fields in the middle, will not cut out prematurely, will not drop empty fields, will not corrupt itself on filename expansions, will not automatically strip leading and trailing whitespace, will not leave a stowaway LF on the end, does not require loops, and does not settle for a single-character delimiter.
Trimming solution
Lastly, I wanted to demonstrate my own fairly intricate trimming solution using the obscure -C callback
option of readarray
. Unfortunately, I’ve run out of room against Stack Overflow’s draconian 30,000 character post limit, so I won’t be able to explain it. I’ll leave that as an exercise for the reader.
function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
Here is a way without setting IFS:
string="1:2:3:4:5"
set -f # avoid globbing (expansion of *).
array=(${string//:/ })
for i in "${!array[@]}"
do
echo "$i=>${array[i]}"
done
The idea is using string replacement:
to replace all matches of $substring with white space and then using the substituted string to initialize a array:
Note: this answer makes use of the split+glob operator. Thus, to prevent expansion of some characters (such as *
) it is a good idea to pause globbing for this script.
I’m starting to learn about writing scripts for the bash terminal, but I can’t work out how to get the comparisons to work properly. The script I’m using is:
echo "enter two numbers";
read a b;
echo "a=$a";
echo "b=$b";
if [ $a \> $b ];
then
echo "a is greater than b";
else
echo "b is greater than a";
fi;
The problem is that it compares the number from the first digit on, i.e. 9 is bigger than 10, but 1 is greater than 09.
How can I convert the numbers into a type to do a true comparison?
In bash, you should do your check in arithmetic context:
For POSIX shells that don’t support (())
, you can use -lt
and -gt
.
You can get a full list of comparison operators with help test
.
Plain and simple
You can check out this cheatsheet if you want more number comparsions in the wonderful world of Bash Scripting.
Shortly, integers can only be compared with:
There is also one nice thing some people might not know about:
This code will print the smallest number out of a
and b
This command lists directories in the current path: ls -d */
What exactly does the pattern */
do?
And how can we give the absolute path in the above command (e.g. ls -d /home/alice/Documents
) for listing only directories in that path?
*/
is a pattern that matches all of the subdirectories in the current directory (*
would match all files and subdirectories; the /
restricts it to directories). Similarly, to list all subdirectories under /home/alice/Documents, use ls -d /home/alice/Documents/*/
echo
Example: echo */
, echo */*/
Here is what I got:
ls
only
Example: ls -d */
Here is exactly what I got:
Or as list (with detail info): ls -dl */
ls
and grep
Example: ls -l | grep "^d"
Here is what I got:
drwxr-xr-x 24 h staff 816 Jun 8 10:55 cs
drwxr-xr-x 6 h staff 204 Jun 8 10:55 draft
drwxr-xr-x 9 h staff 306 Jun 8 10:55 files
drwxr-xr-x 2 h staff 68 Jun 9 13:19 hacks
drwxr-xr-x 6 h staff 204 Jun 8 10:55 masters
drwxr-xr-x 4 h staff 136 Jun 8 10:55 static
Example: for i in $(ls -d */); do echo ${i%%/}; done
Here is what I got:
If you like to have ‘/’ as ending character, the command will be: for i in $(ls -d */); do echo ${i}; done
I use:
This creates a single column with no trailing slash - useful in scripts.
My two cents.
I want to represent multiple conditions like this:
if [ ( $g -eq 1 -a "$c" = "123" ) -o ( $g -eq 2 -a "$c" = "456" ) ]
then
echo abc;
else
echo efg;
fi
but when I execute the script, it shows
where line 15 is the one showing if ….
What is wrong with this condition? I guess something is wrong with the ()
.
Classic technique (escape metacharacters):
if [ \( "$g" -eq 1 -a "$c" = "123" \) -o \( "$g" -eq 2 -a "$c" = "456" \) ]
then echo abc
else echo efg
fi
I’ve enclosed the references to $g
in double quotes; that’s good practice, in general. Strictly, the parentheses aren’t needed because the precedence of -a
and -o
makes it correct even without them.
Note that the -a
and -o
operators are part of the POSIX specification for test
, aka [
, mainly for backwards compatibility (since they were a part of test
in 7th Edition UNIX, for example), but they are explicitly marked as ‘obsolescent’ by POSIX. Bash (see conditional expressions) seems to preempt the classic and POSIX meanings for -a
and -o
with its own alternative operators that take arguments.
With some care, you can use the more modern [[
operator, but be aware that the versions in Bash and Korn Shell (for example) need not be identical.
for g in 1 2 3
do
for c in 123 456 789
do
if [[ ( "$g" -eq 1 && "$c" = "123" ) || ( "$g" -eq 2 && "$c" = "456" ) ]]
then echo "g = $g; c = $c; true"
else echo "g = $g; c = $c; false"
fi
done
done
Example run, using Bash 3.2.57 on Mac OS X:
g = 1; c = 123; true
g = 1; c = 456; false
g = 1; c = 789; false
g = 2; c = 123; false
g = 2; c = 456; true
g = 2; c = 789; false
g = 3; c = 123; false
g = 3; c = 456; false
g = 3; c = 789; false
You don’t need to quote the variables in [[
as you do with [
because it is not a separate command in the same way that [
is.
Isn’t it a classic question?
I would have thought so. However, there is another alternative, namely:
Indeed, if you read the ‘portable shell’ guidelines for the autoconf
tool or related packages, this notation — using ‘||
’ and ‘&&
’ — is what they recommend. I suppose you could even go so far as:
if [ "$g" -eq 1 ] && [ "$c" = "123" ]
then echo abc
elif [ "$g" -eq 2 ] && [ "$c" = "456" ]
then echo abc
else echo efg
fi
Where the actions are as trivial as echoing, this isn’t bad. When the action block to be repeated is multiple lines, the repetition is too painful and one of the earlier versions is preferable — or you need to wrap the actions into a function that is invoked in the different then
blocks.
In Bash:
Using /bin/bash
the following will work:
We’ve got a PHP application and want to count all the lines of code under a specific directory and its subdirectories. We don’t need to ignore comments, as we’re just trying to get a rough idea.
That command works great within a given directory, but ignores subdirectories. I was thinking this might work, but it is returning 74, which is definitely not the case…
What’s the correct syntax to feed in all the files?
Try:
The SLOCCount tool may help as well.
It’ll give an accurate source lines of code count for whatever hierarchy you point it at, as well as some additional stats.
Sorted output: find . -name '*.php' | xargs wc -l | sort -nr
For another one-liner:
works on names with spaces, only outputs one number.
If using a decently recent version of Bash (or ZSH), it’s much simpler:
In the Bash shell this requires the globstar
option to be set, otherwise the **
glob-operator is not recursive. To enable this setting, issue
To make this permanent, add it to one of the initialization files (~/.bashrc
, ~/.bash_profile
etc.).
I have “I love Suzi and Marry” and I want to change “Suzi” to “Sara”.
The result must be like this:
To replace the first occurrence of a pattern with a given string, use ${<em>parameter</em>/<em>pattern</em>/<em>string</em>}
:
#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
echo "${firstString/Suzi/$secondString}"
# prints 'I love Sara and Marry'
To replace all occurrences, use ${<em>parameter</em>//<em>pattern</em>/<em>string</em>}
:
(This is documented in the Bash Reference Manual, §3.5.3 “Shell Parameter Expansion”.)
Note that this feature is not specified by POSIX — it’s a Bash extension — so not all Unix shells implement it. For the relevant POSIX documentation, see The Open Group Technical Standard Base Specifications, Issue 7, the Shell & Utilities volume, §2.6.2 “Parameter Expansion”.
This can be done entirely with bash string manipulation:
That will replace only the first occurrence; to replace them all, double the first slash:
try this:
What is the proper way to modify environment variables like PATH in OS X?
I’ve looked on Google a little bit and found three different files to edit:
I don’t even have some of these files, and I’m pretty sure that .tcshrc is wrong, since OS X uses bash now. Where are these variables, especially PATH, defined?
I’m running OS X v10.5 (Leopard).
Bruno is right on track. I’ve done extensive research and if you want to set variables that are available in all GUI applications, your only option is /etc/launchd.conf
.
Please note that environment.plist does not work for applications launched via Spotlight. This is documented by Steve Sexton here.
Open a terminal prompt
Type sudo vi /etc/launchd.conf
(note: this file might not yet exist)
Put contents like the following into the file
```sh # Set environment variables here so they are available globally to all apps # (and Terminal), including those launched via Spotlight. # # After editing this file run the following command from the terminal to update # environment variables globally without needing to reboot. # NOTE: You will still need to restart the relevant application (including # Terminal) to pick up the changes! # grep -E “^setenv” /etc/launchd.conf | xargs -t -L 1 launchctl # # See http://www.digitaledgesw.com/node/31 # and http://stackoverflow.com/questions/135688/setting-environment-variables-in-os-x/ # # Note that you must hardcode the paths below, don’t use environment variables. # You also need to surround multiple values in quotes, see MAVEN_OPTS example below. # setenv JAVA_VERSION 1.6 setenv JAVA_HOME /System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home setenv GROOVY_HOME /Applications/Dev/groovy setenv GRAILS_HOME /Applications/Dev/grails setenv NEXUS_HOME /Applications/Dev/nexus/nexus-webapp setenv JRUBY_HOME /Applications/Dev/jruby
setenv ANT_HOME /Applications/Dev/apache-ant setenv ANT_OPTS -Xmx512M
setenv MAVEN_OPTS “-Xmx1024M -XX:MaxPermSize=512m” setenv M2_HOME /Applications/Dev/apache-maven
setenv JMETER_HOME /Applications/Dev/jakarta-jmeter ``</li> <li><p>Save your changes in vi and reboot your Mac. Or use the <a href="http://linux.die.net/man/1/grep" rel="noreferrer">
grep</a>/<a href="https://linux.die.net/man/1/xargs" rel="noreferrer">
xargs</a> command which is shown in the code comment above.</p></li> <li><p>Prove that your variables are working by opening a Terminal window and typing
export` and you should see your new variables. These will also be available in IntelliJ IDEA and other GUI applications you launch via Spotlight.
You can set the environment used by launchd (and, by extension, anything started from Spotlight) with launchctl setenv
. For example to set the path:
Or if you want to set up your path in .bashrc
or similar, then have it mirrored in launchd:
There’s no need to reboot though you will need to restart an app if you want it to pick up the changed environment.
This includes any shells already running under Terminal.app, although if you’re there you can set the environment more directly, e.g. with export PATH=/opt/local/bin:/opt/local/sbin:$PATH
for bash or zsh.
To keep changes after a reboot you can set the environment variables from /etc/launchd.conf
, like so:
launchd.conf
is executed automatically when you reboot.
If you want these changes to take effect now, you should use this command to reprocess launchctl.conf
(thanks @mklement for the tip!)
You can find out more about launchctl
and how it loads launchd.conf
with the command man launchctl
.
I would like my Bash script to print an error message if the required argument count is not met.
I tried the following code:
#!/bin/bash
echo Script name: $0
echo $# arguments
if [$# -ne 1];
then echo "illegal number of parameters"
fi
For some unknown reason I’ve got the following error:
What am I doing wrong?
Just like any other simple command, [ ... ]
or test
requires spaces between its arguments.
Or
When in Bash, prefer using [[ ]]
instead as it doesn’t do word splitting and pathname expansion to its variables that quoting may not be necessary unless it’s part of an expression.
It also has some other features like unquoted condition grouping, pattern matching (extended pattern matching with extglob
) and regex matching.
The following example checks if arguments are valid. It allows a single argument or two.
For pure arithmetic expressions, using (( ))
to some may still be better, but they are still possible in [[ ]]
with its arithmetic operators like -eq
, -ne
, -lt
, -le
, -gt
, or -ge
by placing the expression as a single string argument:
That should be helpful if you would need to combine it with other features of [[ ]]
as well.
It’s also logical to make the script exit when invalid parameters are passed to it. This has already been suggested in the comments by ekangas but someone edited this answer to have it with -1
as the returned value, so I might as well do it right.
-1
though accepted by Bash as an argument to exit
is not explicitly documented and is not right to be used as a common suggestion. 64
is also the most formal value since it’s defined in sysexits.h
with #define EX_USAGE 64 /* command line usage error */
. Most tools like ls
also return 2
on invalid arguments. I also used to return 2
in my scripts but lately I no longer really cared, and simply used 1
in all errors. But let’s just place 2
here since it’s most common and probably not OS-specific.
It might be a good idea to use arithmetic expressions if you’re dealing with numbers.
On []: !=, =, == … are string comparison operators and -eq, -gt … are arithmetic binary ones.
I would use:
Or:
To redirect stdout to a truncated file in Bash, I know to use:
To redirect stdout in Bash, appending to a file, I know to use:
To redirect both stdout and stderr to a truncated file, I know to use:
How do I redirect both stdout and stderr appending to a file? cmd &>> file.txt
did not work for me.
Bash executes the redirects from left to right as follows:
>>file.txt
: Open file.txt
in append mode and redirect stdout
there.
2>&1
: Redirect stderr
to “where stdout
is currently going”. In this case, that is a file opened in append mode. In other words, the &1
reuses the file descriptor which stdout
currently uses.
There are two ways to do this, depending on your Bash version.
The classic and portable (Bash pre-4) way is:
A nonportable way, starting with Bash 4 is
(analog to &> outfile
)
For good coding style, you should
If your script already starts with #!/bin/sh
(no matter if intended or not), then the Bash 4 solution, and in general any Bash-specific code, is not the way to go.
Also remember that Bash 4 &>>
is just shorter syntax — it does not introduce any new functionality or anything like that.
The syntax is (beside other redirection syntax) described here: http://bash-hackers.org/wiki/doku.php/syntax/redirection#appending_redirected_output_and_error_output
In Bash you can also explicitly specify your redirects to different files:
Appending would be:
How can I reload .bash_profile
from the command line? I can get the shell to recognize changes to .bash_profile
by exiting and logging back in but I would like to be able to do it on demand.
Simply type source ~/.bash_profile
Alternatively, if you like saving keystrokes you can type . ~/.bash_profile
Just make sure you don’t have any dependencies on the current state in there.
Simply type:
However, if you want to source it to run automatically when terminal starts instead of running it every time you open terminal, you might add . ~/.bash_profile
to ~/.bashrc
file.
Note:
When you open a terminal, the terminal starts bash in (non-login) interactive mode, which means it will source ~/.bashrc
.
~/.bash_profile
is only sourced by bash when started in interactive login mode. That is typically only when you login at the console (Ctrl+Alt+F1..F6), or connecting via ssh
.
I need to write a script that starts my program with different arguments, but I’m new to Bash. I start my program with:
./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt]
.
Here is the pseudocode for what I want to do:
for each filename in /Data do
for int i = 0, i = 3, i++
./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
end for
end for
So I’m really puzzled how to create second argument from the first one, so it looks like dataABCD_Log1.txt and start my program.
A couple of notes first: when you use Data/data1.txt
as an argument, should it really be /Data/data1.txt
(with a leading slash)? Also, should the outer loop scan only for .txt files, or all files in /Data? Here’s an answer, assuming /Data/data1.txt
and .txt files only:
#!/bin/bash
for filename in /Data/*.txt; do
for ((i=0; i<=3; i++)); do
./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
done
done
Notes:
/Data/*.txt
expands to the paths of the text files in /Data (including the /Data/ part)
$( ... )
runs a shell command and inserts its output at that point in the command line
basename somepath .txt
outputs the base part of somepath, with .txt removed from the end (e.g. /Data/file.txt
-> file
)
If you needed to run MyProgram with Data/file.txt
instead of /Data/file.txt
, use "${filename#/}"
to remove the leading slash. On the other hand, if it’s really Data
not /Data
you want to scan, just use for filename in Data/*.txt
.
Sorry for necromancing the thread, but whenever you iterate over files by globbing, it’s good practice to avoid the corner case where the glob does not match (which makes the loop variable expand to the (un-matching) glob pattern string itself).
For example:
Reference: Bash Pitfalls
for file in Data/*.txt
do
for ((i = 0; i < 3; i++))
do
name=${file##*/}
base=${name%.txt}
./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
done
done
The name=${file##*/}
substitution (shell parameter expansion) removes the leading pathname up to the last /
.
The base=${name%.txt}
substitution removes the trailing .txt
. It’s a bit trickier if the extensions can vary.
I want to redirect both stdout and stderr of a process to a single file. How do I do that in Bash?
Take a look here. Should be:
(redirects both stdout
and stderr
to filename).
This is going to redirect stderr to stdout and stdout to some_file
and print it to stdout.
You can redirect stderr to stdout and the stdout into a file:
See http://tldp.org/LDP/abs/html/io-redirection.html
This format is preferred than the most popular &> format that only work in bash. In Bourne shell it could be interpreted as running the command in background. Also the format is more readable 2 (is STDERR) redirected to 1 (STDOUT).
EDIT: changed the order as pointed out in the comments
In a shell script, how do I echo all shell commands called and expand any variable names?
For example, given the following line:
I would like the script to run the command and display the following
The purpose is to save a log of all shell commands called and their arguments. Is there perhaps a better way of generating such a log?
set -x
or set -o xtrace
expands variables and prints a little + sign before the line.
set -v
or set -o verbose
does not expand the variables before printing.
Use set +x
and set +v
to turn off the above settings.
On the first line of the script, one can put #!/bin/sh -x
(or -v
) to have the same effect as set -x
(or -v
) later in the script.
The above also works with /bin/sh
.
See the bash-hackers’ wiki on set
attributes, and on debugging.
set -x
will give you what you want.
Here is an example shell script to demonstrate:
This expands all variables and prints the full commands before output of the command.
output:
You can also toggle this for select lines in your script by wrapping them in set -x
and set +x
e.g.
#!/bin/bash
...
if [[ ! -e $OUT_FILE ]];
then
echo "grabbing $URL"
set -x
curl --fail --noproxy $SERV -s -S $URL -o $OUT_FILE
set +x
fi
How would I get just the current working directory name in a bash script, or even better, just a terminal command.
pwd
gives the full path of the current working directory, e.g. /opt/local/bin
but I only want bin
No need for basename, and especially no need for a subshell running pwd (which adds an extra, and expensive, fork operation); the shell can do this internally using parameter expansion:
result=${PWD##*/} # to assign to a variable
printf '%s\n' "${PWD##*/}" # to print to stdout
# ...more robust than echo for unusual names
# (consider a directory named -e or -n)
printf '%q\n' "${PWD##*/}" # to print to stdout, quoted for use as shell input
# ...useful to make hidden characters readable.
Note that if you’re applying this technique in other circumstances (not PWD
, but some other variable holding a directory name), you might need to trim any trailing slashes. The below uses bash’s extglob support to work even with multiple trailing slashes:
dirname=/path/to/somewhere//
shopt -s extglob # enable +(...) glob syntax
result=${dirname%%+(/)} # trim however many trailing slashes exist
result=${result##*/} # remove everything before the last / that still remains
printf '%s\n' "$result"
Alternatively, without extglob
:
Use the basename
program. For your case:
I can’t seem to work out what the issue with the following if
statement is in regards to the elif
and then
. Keep in mind the printf
is still under development I just haven’t been able to test it yet in the statement so is more than likely wrong.
The error I’m getting is:
./timezone_string.sh: line 14: syntax error near unexpected token `then'
./timezone_string.sh: line 14: `then'
And the statement is like so.
There is a space missing between elif
and [
:
should be
As I see this question is getting a lot of views, it is important to indicate that the syntax to follow is:
meaning that spaces are needed around the brackets. Otherwise, it won’t work. This is because [
itself is a command.
The reason why you are not seeing something like elif[: command not found
(or similar) is that after seeing if
and then
, the shell is looking for either elif
, else
, or fi
. However it finds another then
(after the mis-formatted elif[
). Only after having parsed the statement it would be executed (and an error message like elif[: command not found
would be output).
You have some syntax issues with your script. Here is a fixed version:
[
is a command (or a builtin in some shells). It must be separated by whitespace from the preceding statement:
What’s the simplest way to do a find and replace for a given input string, say abc
, and replace with another string, say XYZ
in file /tmp/file.txt
?
I am writting an app and using IronPython to execute commands through SSH — but I don’t know Unix that well and don’t know what to look for.
I have heard that Bash, apart from being a command line interface, can be a very powerful scripting language. So, if this is true, I assume you can perform actions like these.
Can I do it with bash, and what’s the simplest (one line) script to achieve my goal?
The easiest way is to use sed (or perl):
Which will invoke sed to do an in-place edit due to the -i
option. This can be called from bash.
If you really really want to use just bash, then the following can work:
This loops over each line, doing a substitution, and writing to a temporary file (don’t want to clobber the input). The move at the end just moves temporary to the original name.
File manipulation isn’t normally done by Bash, but by programs invoked by Bash, e.g.:
The -i
flag tells it to do an in-place replacement.
See man perlrun
for more details, including how to take a backup of the original file.
I was surprised because i stumbled over this…
There is a “replace” command which ships with the package “mysql-server”, so if you have installed it try it out:
# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt
# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"
See man replace for more on this…
How would I validate that a program exists, in a way that will either return an error and exit, or continue with the script?
It seems like it should be easy, but it’s been stumping me.
POSIX compatible:
For bash
specific environments:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
Avoid which
. Not only is it an external process you’re launching for doing very little (meaning builtins like hash
, type
or command
are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.
Why care?
which
that doesn’t even set an exit status, meaning the if which foo
won’t even work there and will always report that foo
exists, even if it doesn’t (note that some POSIX shells appear to do this for hash
too).
which
do custom and evil stuff like change the output or even hook into the package manager.
So, don’t use which
. Instead use one of these:
$ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
$ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
$ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
(Minor side-note: some will suggest 2>&-
is the same 2>/dev/null
but shorter – this is untrue. 2>&-
closes FD 2 which causes an error in the program when it tries to write to stderr, which is very different from successfully writing to it and discarding the output (and dangerous!))
If your hash bang is /bin/sh
then you should care about what POSIX says. type
and hash
’s exit codes aren’t terribly well defined by POSIX, and hash
is seen to exit successfully when the command doesn’t exist (haven’t seen this with type
yet). command
’s exit status is well defined by POSIX, so that one is probably the safest to use.
If your script uses bash
though, POSIX rules don’t really matter anymore and both type
and hash
become perfectly safe to use. type
now has a -P
to search just the PATH
and hash
has the side-effect that the command’s location will be hashed (for faster lookup next time you use it), which is usually a good thing since you probably check for its existence in order to actually use it.
As a simple example, here’s a function that runs gdate
if it exists, otherwise date
:
The following is a portable way to check whether a command exists in $PATH
and is executable:
Example:
The executable check is needed because bash returns a non-executable file if no executable file with that name is found in $PATH
.
Also note that if a non-executable file with the same name as the executable exists earlier in $PATH
, dash returns the former, even though the latter would be executed. This is a bug and is in violation of the POSIX standard. [Bug report] [Standard]
In addition, this will fail if the command you are looking for has been defined as an alias.
I agree with lhunath to discourage use of which
, and his solution is perfectly valid for BASH users. However, to be more portable, command -v
shall be used instead:
Command command
is POSIX compliant, see here for its specification: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html
Note: type
is POSIX compliant, but type -P
is not.
I am having trouble coming up with the right combination of semicolons and/or braces. I’d like to do this, but as a one-liner from the command line:
By the way, if you type it as a multiline (as you are showing) at the command prompt and then call the history with arrow up, you will get it on a single line, correctly punctuated.
It’s also possible to use sleep command in while’s condition. Making one-liner looking more clean imho.
Colon is always “true”:
I am running Ubuntu on computer 1 and computer 2. I compiled a C++ program on computer 1, and I can execute it from the terminal using ./program_name
. It runs fine.
However, when I try to do this on computer 2, it says: bash: ./program_name: permission denied
What’s wrong and what can I do about it?
chmod u+x program_name
. Then execute it.
If that does not work, copy the program from the USB device to a native volume on the system. Then chmod u+x program_name
on the local copy and execute that.
Unix and Unix-like systems generally will not execute a program unless it is marked with permission to execute. The way you copied the file from one system to another (or mounted an external volume) may have turned off execute permission (as a safety feature). The command chmod u+x name
adds permission for the user that owns the file to execute it.
That command only changes the permissions associated with the file; it does not change the security controls associated with the entire volume. If it is security controls on the volume that are interfering with execution (for example, a noexec
option may be specified for a volume in the Unix fstab
file, which says not to allow execute permission for files on the volume), then you can remount the volume with options to allow execution. However, copying the file to a local volume may be a quicker and easier solution.
Try this:
Sounds like you don’t have the execute flag set on the file permissions, try:
I just can’t figure out how do I make sure an argument passed to my script is a number or not.
All I want to do is something like this:
Any help?
One approach is to use a regular expression, like so:
If the value is not necessarily an integer, consider amending the regex appropriately; for instance:
…or, to handle numbers with a sign:
Without bashisms (works even in the System V sh),
This rejects empty strings and strings containing non-digits, accepting everything else.
Negative or floating-point numbers need some additional work. An idea is to exclude -
/ .
in the first “bad” pattern and add more “bad” patterns containing the inappropriate uses of them (?*-*
/ *.*.*
)
The following solution can also be used in basic shells such as Bourne without the need for regular expressions. Basically any numeric value evaluation operations using non-numbers will result in an error which will be implicitly considered as false in shell:
as in:
#!/bin/bash
var=a
if [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null; then
echo number
else
echo not a number
fi
You can can also test for $? the return code of the operation which is more explicit:
Redirection of standard error is there to hide the “integer expression expected” message that bash prints out in case we do not have a number.
CAVEATS (thanks to the comments below):
[[ ]]
instead of [ ]
will always evaluate to true
true
bash: [[: 1 a: syntax error in expression (error token is "a")
bash: [[: i: expression recursion level exceeded (error token is "i")
How can I write data to a text file automatically by shell scripting in Linux?
I was able to open the file. However, I don’t know how to write data to it.
#!/bin/sh
FILE="/path/to/file"
/bin/cat <<EOM >$FILE
text1
text2 # This comment will be inside of the file.
The keyword EOM can be any text, but it must start the line and be alone.
EOM # This will be also inside of the file, see the space in front of EOM.
EOM # No comments and spaces around here, or it will not work.
text4
EOM
You can redirect the output of a command to a file:
or append to it
If you want to write directly the command is echo 'text'
I need to transfer a log file to a remote host using sftp from a Linux host. I have been provided credentials for the same from my operations group. However, since I don’t have control over other host, I cannot generate and share RSA keys with the other host.
So is there a way to run the sftp
command (with the username/password provided) from inside the Bash script through a cron job?
I found a similar Stack Overflow question, Specify password to sftp in a Bash script, but there was no satisfactory answer to my problem.
You have a few options other than using public key authentication:
If you decide to give sshpass a chance here is a working script snippet to do so:
Another way would be to use lftp:
The disadvantage of this method is that other users on the computer can read the password from tools like ps
and that the password can become part of your shell history.
A more secure alternative which is available since LFTP 4.5.0 is setting the LFTP_PASSWORD
environment variable and executing lftp with --env-password
. Here’s a full example:
LFTP also includes a cool mirroring feature (can include delete after confirmed transfer ‘–Remove-source-files’):
Expect is a great program to use.
On Ubuntu install it with:
On a CentOS Machine install it with:
Lets say you want to make a connection to a sftp server and then upload a local file from your local machine to the remote sftp server
#!/usr/bin/expect
spawn sftp username@hostname.com
expect "password:"
send "yourpasswordhere\n"
expect "sftp>"
send "cd logdirectory\n"
expect "sftp>"
send "put /var/log/file.log\n"
expect "sftp>"
send "exit\n"
interact
This opens a sftp connection with your password to the server.
Then it goes to the directory where you want to upload your file, in this case “logdirectory”
This uploads a log file from the local directory found at /var/log/ with the files name being file.log to the “logdirectory” on the remote server
I’m writing a script in Bash to test some code. However, it seems silly to run the tests if compiling the code fails in the first place, in which case I’ll just abort the tests.
Is there a way I can do this without wrapping the entire script inside of a while loop and using breaks? Something like a dun dun dun goto?
Try this statement:
Replace 1
with appropriate error codes. See also Exit Codes With Special Meanings.
Use set -e
The script will terminate after the first line that fails (returns nonzero exit code). In this case, command-that-fails2 will not run.
If you were to check the return status of every single command, your script would look like this:
#!/bin/bash
# I'm assuming you're using make
cd /project-dir
make
if [[ $? -ne 0 ]] ; then
exit 1
fi
cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
exit 1
fi
With set -e it would look like:
Any command that fails will cause the entire script to fail and return an exit status you can check with $?. If your script is very long or you’re building a lot of stuff it’s going to get pretty ugly if you add return status checks everywhere.
A bad-arse SysOps guy once taught me the Three-Fingered Claw technique:
These functions are *NIX OS and shell flavor-robust. Put them at the beginning of your script (bash or otherwise), try()
your statement and code on.
(based on flying sheep comment).
yell
: print the script name and all arguments to stderr
:
$0
is the path to the script ;
$*
are all arguments.
>&2
means >
redirect stdout to & pipe 2
. pipe 1
would be stdout
itself.
die
does the same as yell
, but exits with a non-0 exit status, which means “fail”.
try
uses the ||
(boolean OR
), which only evaluates the right side if the left one failed.
$@
is all arguments again, but different.
I have a complex command that I’d like to make a shell/bash script of. I can write it in terms of $1
easily:
I want to be able to pass multiple input names to the script. What’s the right way to do it?
And, of course, I want to handle filenames with spaces in them.
Use "$@"
to represent all the arguments:
This will iterate over each argument and print it out on a separate line. $@ behaves like $* except that when quoted the arguments are broken up properly if there are spaces in them:
Rewrite of a now-deleted answer by VonC.
Robert Gamble’s succinct answer deals directly with the question. This one amplifies on some issues with filenames containing spaces.
See also: ${1:+"$@"} in /bin/sh
Basic thesis: "$@"
is correct, and $*
(unquoted) is almost always wrong. This is because "$@"
works fine when arguments contain spaces, and works the same as $*
when they don’t. In some circumstances, "$*"
is OK too, but "$@"
usually (but not always) works in the same places. Unquoted, $@
and $*
are equivalent (and almost always wrong).
So, what is the difference between $*
, $@
, "$*"
, and "$@"
? They are all related to ‘all the arguments to the shell’, but they do different things. When unquoted, $*
and $@
do the same thing. They treat each ‘word’ (sequence of non-whitespace) as a separate argument. The quoted forms are quite different, though: "$*"
treats the argument list as a single space-separated string, whereas "$@"
treats the arguments almost exactly as they were when specified on the command line. "$@"
expands to nothing at all when there are no positional arguments; "$*"
expands to an empty string — and yes, there’s a difference, though it can be hard to perceive it. See more information below, after the introduction of the (non-standard) command al
.
Secondary thesis: if you need to process arguments with spaces and then pass them on to other commands, then you sometimes need non-standard tools to assist. (Or you should use arrays, carefully: "${array[@]}"
behaves analogously to "$@"
.)
Example:
$ mkdir "my dir" anotherdir
$ ls
anotherdir my dir
$ cp /dev/null "my dir/my file"
$ cp /dev/null "anotherdir/myfile"
$ ls -Fltr
total 0
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir/
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir/
$ ls -Fltr *
my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ ls -Fltr "./my dir" "./anotherdir"
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ var='"./my dir" "./anotherdir"' && echo $var
"./my dir" "./anotherdir"
$ ls -Fltr $var
ls: "./anotherdir": No such file or directory
ls: "./my: No such file or directory
ls: dir": No such file or directory
$
Why doesn’t that work? It doesn’t work because the shell processes quotes before it expands variables. So, to get the shell to pay attention to the quotes embedded in $var
, you have to use eval
:
$ eval ls -Fltr $var
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$
This gets really tricky when you have file names such as “He said,”Don’t do this!“
” (with quotes and double quotes and spaces).
$ cp /dev/null "He said, \"Don't do this!\""
$ ls
He said, "Don't do this!" anotherdir my dir
$ ls -l
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 15:54 He said, "Don't do this!"
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir
$
The shells (all of them) do not make it particularly easy to handle such stuff, so (funnily enough) many Unix programs do not do a good job of handling them. On Unix, a filename (single component) can contain any characters except slash and NUL '\0'
. However, the shells strongly encourage no spaces or newlines or tabs anywhere in a path names. It is also why standard Unix file names do not contain spaces, etc.
When dealing with file names that may contain spaces and other troublesome characters, you have to be extremely careful, and I found long ago that I needed a program that is not standard on Unix. I call it escape
(version 1.1 was dated 1989-08-23T16:01:45Z).
Here is an example of escape
in use - with the SCCS control system. It is a cover script that does both a delta
(think check-in) and a get
(think check-out). Various arguments, especially -y
(the reason why you made the change) would contain blanks and newlines. Note that the script dates from 1992, so it uses back-ticks instead of $(cmd ...)
notation and does not use #!/bin/sh
on the first line.
: "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
# Delta and get files
# Uses escape to allow for all weird combinations of quotes in arguments
case `basename $0 .sh` in
deledit) eflag="-e";;
esac
sflag="-s"
for arg in "$@"
do
case "$arg" in
-r*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
-e) gargs="$gargs `escape \"$arg\"`"
sflag=""
eflag=""
;;
-*) dargs="$dargs `escape \"$arg\"`"
;;
*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
esac
done
eval delta "$dargs" && eval get $eflag $sflag "$gargs"
(I would probably not use escape quite so thoroughly these days - it is not needed with the -e
argument, for example - but overall, this is one of my simpler scripts using escape
.)
The escape
program simply outputs its arguments, rather like echo
does, but it ensures that the arguments are protected for use with eval
(one level of eval
; I do have a program which did remote shell execution, and that needed to escape the output of escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
I have another program called al
that lists its arguments one per line (and it is even more ancient: version 1.1 dated 1987-01-27T14:35:49). It is most useful when debugging scripts, as it can be plugged into a command line to see what arguments are actually passed to the command.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[Added: And now to show the difference between the various "$@"
notations, here is one more example:
$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh * */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$
Notice that nothing preserves the original blanks between the *
and */*
on the command line. Also, note that you can change the ‘command line arguments’ in the shell by using:
This sets 4 options, ‘-new
’, ‘-opt
’, ‘and
’, and ‘arg with space
’.
]
Hmm, that’s quite a long answer - perhaps exegesis is the better term. Source code for escape
available on request (email to firstname dot lastname at gmail dot com). The source code for al
is incredibly simple:
That’s all. It is equivalent to the test.sh
script that Robert Gamble showed, and could be written as a shell function (but shell functions didn’t exist in the local version of Bourne shell when I first wrote al
).
Also note that you can write al
as a simple shell script:
The conditional is needed so that it produces no output when passed no arguments. The printf
command will produce a blank line with only the format string argument, but the C program produces nothing.
Note that Robert’s answer is correct, and it works in sh
as well. You can (portably) simplify it even further:
is equivalent to:
I.e., you don’t need anything!
Testing ($
is command prompt):
$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d
I first read about this in Unix Programming Environment by Kernighan and Pike.
In bash
, help for
documents this:
If
for NAME [in WORDS ... ;] do COMMANDS; done
'in WORDS ...;'
is not present, then'in "$@"'
is assumed.
I have made Bash scripts before and they all ran fine without this at the beginning. What’s the point of putting it in? Would things be any different?
Also, how do you pronounce #
? I know that !
is pronounced as “bang.”
How is #!
pronounced?
It’s a convention so the *nix shell knows what kind of interpreter to run.
For example, older flavors of ATT defaulted to sh (the Bourne shell), while older versions of BSD defaulted to csh (the C shell).
Even today (where most systems run bash, the “Bourne Again Shell”), scripts can be in bash, python, perl, ruby, PHP, etc, etc. For example, you might see #!/bin/perl
or #!/bin/perl5
.
PS: The exclamation mark (!
) is affectionately called “bang”. The shell comment symbol (#
) is sometimes called “hash”.
PPS: Remember - under *nix, associating a suffix with a file type is merely a convention, not a “rule”. An executable can be a binary program, any one of a million script types and other things as well. Hence the need for #!/bin/bash
.
To be more precise the shebang #!
, when it is the first two bytes of an executable (x
mode) file, is interpreted by the execve(2) system call (which execute programs). But POSIX specification for execve
don’t mention the shebang.
It must be followed by a file path of an interpreter executable (which BTW could even be relative, but most often is absolute).
A nice trick (or perhaps not so nice one) to find an interpreter (e.g. python
) in the user’s $PATH
is to use the env
program (always at /usr/bin/env
on all Linux) like e.g.
Any ELF executable can be an interpreter. You could even use #!/bin/cat
or #!/bin/true
if you wanted to! (but that would be often useless)
It’s called a shebang. In unix-speak, # is called sharp (like in music) or hash (like hashtags on twitter), and ! is called bang. (You can actually reference your previous shell command with !!, called bang-bang). So when put together, you get haSH-BANG, or shebang.
The part after the #! tells Unix what program to use to run it. If it isn’t specified, it will try with bash (or sh, or zsh, or whatever your $SHELL variable is) but if it’s there it will use that program. Plus, # is a comment in most languages, so the line gets ignored in the subsequent execution.
I have this script, but I do not know how to get the last element in the printout:
The last element should be the number of CPUs, minus 1.
or simply
which will count the number of lines starting with “processor” in /proc/cpuinfo
For systems with hyper-threading, you can use
which should return (for example) 8
(whereas the command above would return 16
)
Processing the contents of /proc/cpuinfo
is needlessly baroque. Use nproc which is part of coreutils, so it should be available on most Linux installs.
Command nproc
prints the number of processing units available to the current process, which may be less than the number of online processors.
To find the number of all installed cores/processors use nproc --all
On my 8-core machine:
The most portable solution I have found is the getconf
command:
This works on both Linux and Mac OS X. Another benefit of this over some of the other approaches is that getconf has been around for a long time. Some of the older Linux machines I have to do development on don’t have the nproc
or lscpu
commands available, but they have getconf
.
Editor’s note: While the getconf
utility is POSIX-mandated, the specific _NPROCESSORS_ONLN
and _NPROCESSORS_CONF
values are not. That said, as stated, they work on Linux platforms as well as on macOS; on FreeBSD/PC-BSD, you must omit the leading _
.
In bash, calling foo
would display any output from that command on the stdout.
Calling foo > output
would redirect any output from that command to the file specified (in this case ‘output’).
Is there a way to redirect output to a file and have it display on stdout?
The command you want is named tee
:
For example, if you only care about stdout:
If you want to include stderr, do:
2>&1
redirects channel 2 (stderr/standard error) into channel 1 (stdout/standard output), such that both is written as stdout. It is also directed to the given output file as of the tee
command.
Furthermore, if you want to append to the log file, use tee -a
as:
2>&1
dumps the stderr and stdout streams. tee outfile
takes the stream it gets and writes it to the screen and to the file “outfile”.
This is probably what most people are looking for. The likely situation is some program or script is working hard for a long time and producing a lot of output. The user wants to check it periodically for progress, but also wants the output written to a file.
The problem (especially when mixing stdout and stderr streams) is that there is reliance on the streams being flushed by the program. If, for example, all the writes to stdout are not flushed, but all the writes to stderr are flushed, then they’ll end up out of chronological order in the output file and on the screen.
It’s also bad if the program only outputs 1 or 2 lines every few minutes to report progress. In such a case, if the output was not flushed by the program, the user wouldn’t even see any output on the screen for hours, because none of it would get pushed through the pipe for hours.
Update: The program unbuffer
, part of the expect
package, will solve the buffering problem. This will cause stdout and stderr to write to the screen and file immediately and keep them in sync when being combined and redirected to tee
. E.g.:
Another way that works for me is,
as shown in gnu bash manual
Example:
If ‘|&’ is used, command1’s standard error, in addition to its standard output, is connected to command2’s standard input through the pipe; it is shorthand for 2>&1 |. This implicit redirection of the standard error to the standard output is performed after any redirections specified by the command.
For more information, refer redirection
I would like to check if a string begins with “node” e.g. “node001”. Something like
How can I do it correctly?
I further need to combine expressions to check if HOST is either “user1” or begins with “node”
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi
> > > -bash: [: too many arguments
How to do it correctly?
This snippet on the Advanced Bash Scripting Guide says:
# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.
[[ $a == z* ]] # True if $a starts with a "z" (wildcard matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).
So you had it nearly correct; you needed double brackets, not single brackets.
With regards to your second question, you can write it this way:
HOST=user1
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
echo yes1
fi
HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
echo yes2
fi
Which will echo
Bash’s if
syntax is hard to get used to (IMO).
If you’re using a recent bash (v3+), I suggest bash regex comparison operator =~
, i.e.
To match this or that
in a regex use |
, i.e.
Note - this is ‘proper’ regular expression syntax.
user*
means use
and zero-or-more occurrences of r
, so use
and userrrr
will match.
user.*
means user
and zero-or-more occurrences of any character, so user1
, userX
will match.
^user.*
means match the pattern user.*
at the begin of $HOST.
If you’re not familiar with regular expression syntax, try referring to this resource.
Note - it’s better if you ask each new question as a new question, it makes stackoverflow tidier and more useful. You can always include a link back to a previous question for reference.
I always try to stick with POSIX sh instead of using bash extensions, since one of the major points of scripting is portability. (Besides connecting programs, not replacing them)
In sh, there is an easy way to check for an “is-prefix” condition.
Given how old, arcane and crufty sh is (and bash is not the cure: It’s more complicated, less consistent and less portable), I’d like to point out a very nice functional aspect: While some syntax elements like case
are built-in, the resulting constructs are no different than any other job. They can be composed in the same way:
Or even shorter
Or even shorter (just to present !
as a language element – but this is bad style now)
If you like being explicit, build your own language element:
Isn’t this actually quite nice?
And since sh is basically only jobs and string-lists (and internally processes, out of which jobs are composed), we can now even do some light functional programming:
beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }
all() {
test=$1; shift
for i in "$@"; do
$test "$i" || return
done
}
all "beginswith x" x xy xyz ; checkresult # prints TRUE
all "beginswith x" x xy abc ; checkresult # prints FALSE
This is elegant. Not that I’d advocate using sh for anything serious – it breaks all too quickly on real world requirements (no lambdas, so must use strings. But nesting function calls with strings is not possible, pipes are not possible…)
How can I get ls to spit out a flat list of recursive one-per-line paths?
For example, I just want a flat listing of files with their full paths:
/home/dreftymac/.
/home/dreftymac/foo.txt
/home/dreftymac/bar.txt
/home/dreftymac/stackoverflow
/home/dreftymac/stackoverflow/alpha.txt
/home/dreftymac/stackoverflow/bravo.txt
/home/dreftymac/stackoverflow/charlie.txt
ls -a1
almost does what I need, but I do not want path fragments, I want full paths.
If you really want to use ls
, then format its output using awk:
Use find:
If you want files only (omit directories, devices, etc):
ls -ld $(find .)
if you want to sort your output by modification time:
ls -ltd $(find .)
I have a shell script which I want to run without using the “sh” or “bash” commands. For example:
Instead of: sh script.sh
I want to use: script.sh
How can I do this?
P.S. (i) I don’t use shell script much and I tried reading about aliases, but I did not understand how to use them.
Add a “shebang” at the top of your file:
And make your file executable (chmod +x script.sh
).
Finally, modify your path to add the directory where your script is located:
(typically, you want $HOME/bin
for storing your own scripts)
These are some of the prerequisites of directly using the script name:
she-bang (#!/bin/bash)
line at the very top.
chmod u+x scriptname
make the script executable. (where scriptname
is the name of your script)
/usr/local/bin
folder.
Note: The reason I suggested to place it under /usr/local/bin
folder is because most likely that will be path already added to your PATH variable
.
If you don’t have access to the /usr/local/bin
folder then do the following:
myscripts
.
ls -lart
on your home directory, to identify the start-up script your shell is using. It should either be .profile
or .bashrc
.
export set PATH=$PATH:~/myscript
.
scriptname
.
Just make sure it is executable, using chmod +x
. By default, the current directory is not on your PATH, so you will need to execute it as ./script.sh
- or otherwise reference it by a qualified path. Alternatively, if you truly need just script.sh
, you would need to add it to your PATH. (You may not have access to modify the system path, but you can almost certainly modify the PATH of your own current environment.) This also assumes that your script starts with something like #!/bin/sh
.
You could also still use an alias, which is not really related to shell scripting but just the shell, and is simple as:
Which would allow you to use just simply script.sh
(literally - this won’t work for any other *.sh
file) instead of sh script.sh
.
I have a file that contains directory names:
my_list.txt
:
I’d like to check in Bash before I’ll add a directory name if that name already exists in the file.
The exit status is 0 (true) if the name was found, 1 (false) if not, so:
Here are the relevant sections of the man page for grep
:
-F
,--fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of which is to be matched.
-x
,--line-regexp
Select only those matches that exactly match the whole line.
Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected. Also see the
-q
,--quiet
,--silent
-s
or--no-messages
option.
As rightfully pointed out in the comments, the above approach silently treats error cases as if the string was found. If you want to handle errors in a different way, you’ll have to omit the -q
option, and detect errors based on the exit status:
Normally, the exit status is 0 if selected lines are found and 1 otherwise. But the exit status is 2 if an error occurred, unless the-q
or--quiet
or--silent
option is used and a selected line is found. Note, however, that POSIX only mandates, for programs such asgrep
,cmp
, anddiff
, that the exit status in case of error be greater than 1; it is therefore advisable, for the sake of portability, to use logic that tests for this general condition instead of strict equality with 2.
To suppress the normal output from grep
, you can redirect it to /dev/null
. Note that standard error remains undirected, so any error messages that grep
might print will end up on the console as you’d probably want.
To handle the three cases, we can use a case
statement:
Regarding the following solution:
In case you are wondering (as I did) what -Fxq
means in plain English:
F
: Affects how PATTERN is interpreted (fixed string instead of a regex)
x
: Match whole line
q
: Shhhhh… minimal printing
From the man file:
-F, --fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of which is to be matched.
(-F is specified by POSIX.)
-x, --line-regexp
Select only those matches that exactly match the whole line. (-x is specified by POSIX.)
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero status if any match is
found, even if an error was detected. Also see the -s or --no-messages option. (-q is specified by
POSIX.)
Three methods in my mind:
2) Short test for a string in a file
3) Longer bash script using regex:
#!/bin/bash
declare file="content.txt"
declare regex="\s+string\s+"
declare file_content=$( cat "${file}" )
if [[ " $file_content " =~ $regex ]] # please note the space before and after the file content
then
echo "found"
else
echo "not found"
fi
exit
This should be quicker if you have to test multiple string on a file content using a loop for example changing the regex at any cicle.
Is there a way to do something like PHPs $array[] = 'foo';
in bash vs doing:
Yes there is:
In the context where an assignment statement is assigning a value to a shell variable or array index (see Arrays), the ‘+=’ operator can be used to append to or add to the variable’s previous value.
As Dumb Guy points out, it’s important to note whether the array starts at zero and is sequential. Since you can make assignments to and unset non-contiguous indices ${#array[@]}
is not always the next item at the end of the array.
$ array=(a b c d e f g h)
$ array[42]="i"
$ unset array[2]
$ unset array[3]
$ declare -p array # dump the array so we can see what it contains
declare -a array='([0]="a" [1]="b" [4]="e" [5]="f" [6]="g" [7]="h" [42]="i")'
$ echo ${#array[@]}
7
$ echo ${array[${#array[@]}]}
h
Here’s how to get the last index:
$ end=(${!array[@]}) # put all the indices in an array
$ end=${end[@]: -1} # get the last one
$ echo $end
42
That illustrates how to get the last element of an array. You’ll often see this:
As you can see, because we’re dealing with a sparse array, this isn’t the last element. This works on both sparse and contiguous arrays, though:
$ declare -a arr
$ arr=("a")
$ arr=("${arr[@]}" "new")
$ echo ${arr[@]}
a new
$ arr=("${arr[@]}" "newest")
$ echo ${arr[@]}
a new newest
I am trying to create a timestamp variable in a shell script to make the logging a little easier. I want to create the variable at the beginning of the script and have it print out the current time whenever I issue echo $timestamp
. It proving to be more difficult then I thought. Here are some things I’ve tried:
timestamp="(date +"%T")"
echo prints out (date +"%T")
timestamp="$(date +"%T")"
echo prints the time when the variable was initialized.
Other things I’ve tried are just slight variations that didn’t work any better. Does anyone know how to accomplish what I’m trying to do?
In order to get the current timestamp and not the time of when a fixed variable is defined, the trick is to use a function and not a variable:
#!/bin/bash
# Define a timestamp function
timestamp() {
date +"%T"
}
# do something...
timestamp # print timestamp
# do something else...
timestamp # print another timestamp
# continue...
If you don’t like the format given by the %T
specifier you can combine the other time conversion specifiers accepted by date
. For GNU date
, you can find the complete list of these specifiers in the official documentation here: https://www.gnu.org/software/coreutils/manual/html_node/Time-conversion-specifiers.html#Time-conversion-specifiers
If you want to get unix timestamp, then you need to use:
%T
will give you just the time; same as %H:%M:%S
(via http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/)
How can I run .sh on Windows 7 Command Prompt? I always get this error when I try to run this line in it,
error,
or,
error,
Any ideas what have I missed?
Here the screen grab,
The error message indicates that you have not installed bash
, or it is not in your PATH
.
The top Google hit is http://win-bash.sourceforge.net/ but you also need to understand that most Bash scripts expect a Unix-like environment; so just installing Bash is probably unlikely to allow you to run a script you found on the net, unless it was specifically designed for this particular usage scenario. The usual solution to that is https://www.cygwin.com/ but there are many possible alternatives, depending on what exactly it is that you want to accomplish.
If Windows is not central to your usage scenario, installing a free OS (perhaps virtualized) might be the simplest way forward.
The second error message is due to the fact that Windows nominally accepts forward slash as a directory separator, but in this context, it is being interpreted as a switch separator. In other words, Windows parses your command line as app /build /build.sh
(or, to paraphrase with Unix option conventions, app --build --build.sh
). You could try app\build\build.sh
but it is unlikely to work, because of the circumstances outlined above.
Install GIT. During installation of GIT, add GIT Bash to windows context menu by selecting its option. After installation right click in your folder select GIT Bash Here
(see attached pic) and use your sh command like for example:
The most common way to run a .sh file is using the sh command:
other good option is installing CygWin
in Windows
the home
is located in:
for example i execute my my-script-test.sh
file using the bash command as:
How is it possible to change the default shell? The env
command currently says:
and I want to change that to Bash.
Try linux command chsh
.
The detailed command is chsh -s /bin/bash
. It will prompt you to enter your password. Your default login shell is /bin/bash
now. You must log out and log back in to see this change.
The following is quoted from man page:
The chsh command changes the user login shell. This determines the name of the users initial login command. A normal user may only change the login shell for her own account, the superuser may change the login shell for any account
This command will change the default login shell permanently.
Note: If your user account is remote such as on Kerberos authentication (e.g. Enterprise RHEL) then you will not be able to use chsh
.
You can change the passwd file directly for the particular user or use the below command
Then log out and log in
You should have a ‘skeleton’ somewhere in /etc
, probably /etc/skeleton
, or check the default settings, probably /etc/default
or something. Those are scripts that define standard environment variables getting set during a login.
If it is just for your own account: check the (hidden) file ~/.profile
and ~/.login
. Or generate them, if they don’t exist. These are also evaluated by the login process.
Is there a standard Bash tool that acts like echo but outputs to stderr rather than stdout?
I know I can do echo foo 1>&2
but it’s kinda ugly and, I suspect, error prone (e.g. more likely to get edited wrong when things change).
You could do this, which facilitates reading:
>&2
copies file descriptor #2 to file descriptor #1. Therefore, after this redirection is performed, both file descriptors will refer to the same file: the one file descriptor #2 was originally referring to. For more information see the Bash Hackers Illustrated Redirection Tutorial.
You could define a function:
This would be faster than a script and have no dependencies.
Camilo Martin’s bash specific suggestion uses a “here string” and will print anything you pass to it, including arguments (-n) that echo would normally swallow:
Glenn Jackman’s solution also avoids the argument swallowing problem:
Since 1
is the standard output, you do not have to explicitly name it in front of an output redirection like >
but instead can simply type:
echo This message goes to stderr >&2
Since you seem to be worried that 1>&2
will be difficult for you to reliably type, the elimination of the redundant 1
might be a slight encouragement to you!
I’m trying to write a script that will check two error flags, and in case one flag (or both) are changed it’ll echo– error happened. My script:
my_error_flag=0
my_error_flag_o=0
do something.....
if [[ "$my_error_flag"=="1" || "$my_error_flag_o"=="2" ] || [ "$my_error_flag"="1" && "$my_error_flag_o"="2" ]]; then
echo "$my_error_flag"
else
echo "no flag"
fi
Basically, it should be, something along:
The error I get is:
line 26: conditional binary operator expected
line 26: syntax error near `]'
line 26: `if [[ "$my_error_flag"=="1" || "$my_error_flag_o"=="2" ] || [ "$my_error_flag"="1" && "$my_error_flag_o"="2" ]]; then'
Are my brackets messed up?
Use -a
(for and) and -o
(for or) operations.
tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html
Update
Actually you could still use &&
and ||
with the -eq
operation. So your script would be like this:
my_error_flag=1
my_error_flag_o=1
if [ $my_error_flag -eq 1 ] || [ $my_error_flag_o -eq 2 ] || ([ $my_error_flag -eq 1 ] && [ $my_error_flag_o -eq 2 ]); then
echo "$my_error_flag"
else
echo "no flag"
fi
Although in your case you can discard the last two expressions and just stick with one or operation like this:
You can use either [[
or ((
keyword. When you use [[
keyword, you have to use string operators such as -eq
, -lt
. I think, ((
is most preferred for arithmetic, because you can directly use operators such as ==
, <
and >
.
Using [[
operator
a=$1
b=$2
if [[ a -eq 1 || b -eq 2 ]] || [[ a -eq 3 && b -eq 4 ]]
then
echo "Error"
else
echo "No Error"
fi
Using ((
operator
a=$1
b=$2
if (( a == 1 || b == 2 )) || (( a == 3 && b == 4 ))
then
echo "Error"
else
echo "No Error"
fi
Do not use -a
or -o
operators Since it is not Portable.
Please try following
if ([ $dateR -ge 234 ] && [ $dateR -lt 238 ]) || ([ $dateR -ge 834 ] && [ $dateR -lt 838 ]) || ([ $dateR -ge 1434 ] && [ $dateR -lt 1438 ]) || ([ $dateR -ge 2034 ] && [ $dateR -lt 2038 ]) ;
then
echo "WORKING"
else
echo "Out of range!"
Given following command:
why does awk output:
“-F” is a command line argument not awk syntax, try:
If you want to do it programatically, you can use the FS
variable:
Note that if you change it in the main loop rather than the BEGIN
loop, it takes affect for the next line read in, since the current line has already been split.
You have multiple ways to set :
as separator:
All of them are equivalent and for an will return 1
for a sample input “1:2:3”:
$ awk -F: '{print $1}' <<< "1:2:3"
1
$ awk -v FS=: '{print $1}' <<< "1:2:3"
1
$ awk '{print $1}' FS=: <<< "1:2:3"
1
$ awk 'BEGIN{FS=":"} {print $1}' <<< "1:2:3"
1
I need to repeatedly remove the first line from a huge text file using a bash script.
Right now I am using sed -i -e "1d" $FILE
- but it takes around a minute to do the deletion.
Is there a more efficient way to accomplish this?
Try tail:
-n x
: Just print the last x
lines. tail -n 5
would give you the last 5 lines of the input. The +
sign kind of inverts the argument and make tail
print anything but the first x-1
lines. tail -n +1
would print the whole file, tail -n +2
everything but the first line, etc.
GNU tail
is much faster than sed
. tail
is also available on BSD and the -n +2
flag is consistent across both tools. Check the FreeBSD or OS X man pages for more.
The BSD version can be much slower than sed
, though. I wonder how they managed that; tail
should just read a file line by line while sed
does pretty complex operations involving interpreting a script, applying regular expressions and the like.
Note: You may be tempted to use
but this will give you an empty file. The reason is that the redirection (>
) happens before tail
is invoked by the shell:
$FILE
tail
tail
process to $FILE
tail
reads from the now empty $FILE
If you want to remove the first line inside the file, you should use:
The &&
will make sure that the file doesn’t get overwritten when there is a problem.
You can use -i to update the file without using ‘>’ operator. The following command will delete the first line from the file and save it to the file.
For those who are on SunOS which is non-GNU, the following code will help:
I’ve been trying to install lpng142 on my fed 12 system. Seems like a problem to me. I get this error
[root@localhost lpng142]# ./configure
bash: ./configure: /bin/sh^M: bad interpreter: No such file or directory
[root@localhost lpng142]#
How do I fix this? The /etc/fstab
file:
#
# /etc/fstab
# Created by anaconda on Wed May 26 18:12:05 2010
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/VolGroup-lv_root / ext4 defaults 1 1
UUID=ce67cf79-22c3-45d4-8374-bd0075617cc8 /boot ext4
defaults 1 2
/dev/mapper/VolGroup-lv_swap swap swap defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
To fix, open your script with vi or vim and enter in vi command mode (key Esc), then type this:
Finally save it
:x!
or :wq!
Looks like you have a dos line ending file. The clue is the ^M
.
You need to re-save the file using Unix line endings.
You might have a dos2unix
command line utility that will also do this for you.
Or if you want to do this with a script:
How do I find and replace every occurrence of:
with
in every text file under the /home/www/
directory tree recursively?
From man find
:
-print0 (GNUfind
only) tellsfind
to use the null character (\0
) instead of whitespace as the output delimiter between pathnames found. This is a safer option if your files can contain blanks or other special characters. It is recommended to use the-print0
argument tofind
if you use-exec <command>
orxargs
(the-0
argument is needed inxargs
.)
Note: Do not run this command on a folder including a git repo - changes to .git could corrupt your git index.
Compared to other answers here, this is simpler than most and uses sed instead of perl, which is what the original question asked for.
I am looking for a command line solution that would return me the primary (first) IP address of the localhost, other than 127.0.0.1
The solution should work at least for Linux (Debian and RedHat) and OS X 10.7+
I am aware that ifconfig
is available on both but its output is not so consistent between these platforms.
Use grep
to filter IP address from ifconfig
:
ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'
Or with sed
:
ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'
If you are only interested in certain interfaces, wlan0, eth0, etc. then:
ifconfig wlan0 | ...
You can alias the command in your .bashrc
to create your own command called myip
for instance.
alias myip="ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'"
A much simpler way is hostname -I
(hostname -i
for older versions of hostname
but see comments). However, this is on Linux only.
For linux machines (not OS X) :
The following will work on Linux but not OSX.
This doesn’t rely on DNS at all, and it works even if /etc/hosts
is not set correctly (1
is shorthand for 1.0.0.0
):
or avoiding awk
and using Google’s public DNS at 8.8.8.8
for obviousness:
A less reliable way: (see comment below)
How do I append the output of a command to the end of a text file?
Use >>
instead of >
when directing output to a file:
If file_to_append_to
does not exist, it will be created.
Example:
To append
a file use >>
then the output should be
To overwrite
a file use >
then the out put is
hello tom
If I want find the differences between two directory trees, I usually just execute:
This outputs exactly what the differences are between corresponding files. I’m interested in just getting a list of corresponding files whose content differs. I assumed that this would simply be a matter of passing a command line option to diff
, but I couldn’t find anything on the man page.
Any suggestions?
You said Linux, so you luck out (at least it should be available, not sure when it was added):
Should do what you need.
If you also want to see differences for files that may not exist in either directory:
The command I use is:
It is exactly the same as Mark’s :) But his answer bothered me as it uses different types of flags, and it made me look twice. Using Mark’s more verbose flags it would be:
I apologise for posting when the other answer is perfectly acceptable. Could not stop myself… working on being less pedantic.
I like to use git diff --no-index dir1/ dir2/
, because it can show the differences in color (if you have that option set in your git config) and because it shows all of the differences in a long paged output using “less”.
I need to hide all permission denied messages from:
I am experimenting when such message arises. I need to gather all folders and files, to which it does not arise.
Is it possible to direct the permission levels to the files_and_folders
file?
How can I hide the errors at the same time?
Note:
* This answer probably goes deeper than the use case warrants, and find 2>/dev/null
may be good enough in many situations. It may still be of interest for a cross-platform perspective and for its discussion of some advanced shell techniques in the interest of finding a solution that is as robust as possible, even though the cases guarded against may be largely hypothetical.
* If your system is configured to show localized error messages, prefix the find
calls below with LC_ALL=C
(LC_ALL=C find ...
) to ensure that English messages are reported, so that grep -v 'Permission denied'
works as intended. Invariably, however, any error messages that do get displayed will then be in English as well.
If your shell is bash
or zsh
, there’s a solution that is robust while being reasonably simple, using only POSIX-compliant find
features; while bash
itself is not part of POSIX, most modern Unix platforms come with it, making this solution widely portable:
Note: There’s a small chance that some of grep
’s output may arrive after find
completes, because the overall command doesn’t wait for the command inside >(...)
to finish. In bash
, you can prevent this by appending | cat
to the command.
>(...)
is a (rarely used) output process substitution that allows redirecting output (in this case, stderr output (2>
) to the stdin of the command inside >(...)
.
In addition to bash
and zsh
, ksh
supports them as well in principle, but trying to combine them with redirection from stderr, as is done here (2> >(...)
), appears to be silently ignored (in ksh 93u+
).
grep -v 'Permission denied'
filters out (-v
) all lines (from the find
command’s stderr stream) that contain the phrase Permission denied
and outputs the remaining lines to stderr (>&2
).
This approach is:
robust: grep
is only applied to error messages (and not to a combination of file paths and error messages, potentially leading to false positives), and error messages other than permission-denied ones are passed through, to stderr.
side-effect free: find
’s exit code is preserved: the inability to access at least one of the filesystem items encountered results in exit code 1
(although that won’t tell you whether errors other than permission-denied ones occurred (too)).
Fully POSIX-compliant solutions either have limitations or require additional work.
If find
’s output is to be captured in a file anyway (or suppressed altogether), then the pipeline-based solution from Jonathan Leffler’s answer is simple, robust, and POSIX-compliant:
Note that the order of the redirections matters: 2>&1
must come first.
Capturing stdout output in a file up front allows 2>&1
to send only error messages through the pipeline, which grep
can then unambiguously operate on.
The only downside is that the overall exit code will be the grep
command’s, not find
’s, which in this case means: if there are no errors at all or only permission-denied errors, the exit code will be 1
(signaling failure), otherwise (errors other than permission-denied ones) 0
- which is the opposite of the intent.
That said, find
’s exit code is rarely used anyway, as it often conveys little information beyond fundamental failure such as passing a non-existent path.
However, the specific case of even only some of the input paths being inaccessible due to lack of permissions is reflected in find
’s exit code (in both GNU and BSD find
): if a permissions-denied error occurs for any of the files processed, the exit code is set to 1
.
The following variation addresses that:
Now, the exit code indicates whether any errors other than Permission denied
occurred: 1
if so, 0
otherwise.
In other words: the exit code now reflects the true intent of the command: success (0
) is reported, if no errors at all or only permission-denied errors occurred.
This is arguably even better than just passing find
’s exit code through, as in the solution at the top.
gniourf_gniourf in the comments proposes a (still POSIX-compliant) generalization of this solution using sophisticated redirections, which works even with the default behavior of printing the file paths to stdout:
In short: Custom file descriptor 3
is used to temporarily swap stdout (1
) and stderr (2
), so that error messages alone can be piped to grep
via stdout.
Without these redirections, both data (file paths) and error messages would be piped to grep
via stdout, and grep
would then not be able to distinguish between error message Permission denied
and a (hypothetical) file whose name happens to contain the phrase Permission denied
.
As in the first solution, however, the the exit code reported will be grep
’s, not find
’s, but the same fix as above can be applied.
There are several points to note about Michael Brux’s answer, find . ! -readable -prune -o -print
:
It requires GNU find
; notably, it won’t work on macOS. Of course, if you only ever need the command to work with GNU find
, this won’t be a problem for you.
Some Permission denied
errors may still surface: find ! -readable -prune
reports such errors for the child items of directories for which the current user does have r
permission, but lacks x
(executable) permission. The reason is that because the directory itself is readable, -prune
is not executed, and the attempt to descend into that directory then triggers the error messages. That said, the typical case is for the r
permission to be missing.
Note: The following point is a matter of philosophy and/or specific use case, and you may decide it is not relevant to you and that the command fits your needs well, especially if simply printing the paths is all you do:
find
command, then the opposite approach of proactively preventing permission-denied errors requires introducing “noise” into the find
command, which also introduces complexity and logical pitfalls.
-name
filter, as follows:find . ! -readable -prune -o -name '*.txt'
-print
action is required (an explanation can be found in this answer). Such subtleties can introduce bugs.
The first solution in Jonathan Leffler’s answer, find . 2>/dev/null > files_and_folders
, as he himself states, blindly silences all error messages (and the workaround is cumbersome and not fully robust, as he also explains). Pragmatically speaking, however, it is the simplest solution, as you may be content to assume that any and all errors would be permission-related.
mist’s answer, sudo find . > files_and_folders
, is concise and pragmatic, but ill-advised for anything other than merely printing filenames, for security reasons: because you’re running as the root user, “you risk having your whole system being messed up by a bug in find or a malicious version, or an incorrect invocation which writes something unexpectedly, which could not happen if you ran this with normal privileges” (from a comment on mist’s answer by tripleee).
The 2nd solution in viraptor’s answer, find . 2>&1 | grep -v 'Permission denied' > some_file
runs the risk of false positives (due to sending a mix of stdout and stderr through the pipeline), and, potentially, instead of reporting non-permission-denied errors via stderr, captures them alongside the output paths in the output file.
Use:
This hides not just the Permission denied
errors, of course, but all error messages.
If you really want to keep other possible errors, such as too many hops on a symlink, but not the permission denied ones, then you’d probably have to take a flying guess that you don’t have many files called ‘permission denied’ and try:
If you strictly want to filter just standard error, you can use the more elaborate construction:
The I/O redirection on the find
command is: 2>&1 > files_and_folders |
. The pipe redirects standard output to the grep
command and is applied first. The 2>&1
sends standard error to the same place as standard output (the pipe). The > files_and_folders
sends standard output (but not standard error) to a file. The net result is that messages written to standard error are sent down the pipe and the regular output of find
is written to the file. The grep
filters the standard output (you can decide how selective you want it to be, and may have to change the spelling depending on locale and O/S) and the final >&2
means that the surviving error messages (written to standard output) go to standard error once more. The final redirection could be regarded as optional at the terminal, but would be a very good idea to use it in a script so that error messages appear on standard error.
There are endless variations on this theme, depending on what you want to do. This will work on any variant of Unix with any Bourne shell derivative (Bash, Korn, …) and any POSIX-compliant version of find
.
If you wish to adapt to the specific version of find
you have on your system, there may be alternative options available. GNU find
in particular has a myriad options not available in other versions — see the currently accepted answer for one such set of options.
Use:
or more generally
Works with: find (GNU findutils) 4.4.2. Background:
-readable
test matches readable files. The !
operator returns true, when test is false. And ! -readable
matches not readable directories (&files).
-prune
action does not descend into directory.
! -readable -prune
can be translated to: if directory is not readable, do not descend into it.
-readable
test takes into account access control lists and other permissions artefacts which the -perm
test ignores.
See also find
(1) manpage for many more details.
How to wait in a bash script for several subprocesses spawned from that script to finish and return exit code !=0 when any of the subprocesses ends with code !=0 ?
Simple script:
The above script will wait for all 10 spawned subprocesses, but it will always give exit status 0 (see help wait
). How can I modify this script so it will discover exit statuses of spawned subprocesses and return exit code 1 when any of subprocesses ends with code !=0?
Is there any better solution for that than collecting PIDs of the subprocesses, wait for them in order and sum exit statuses?
wait
also (optionally) takes the PID of the process to wait for, and with $! you get the PID of the last command launched in background. Modify the loop to store the PID of each spawned sub-process into an array, and then loop again waiting on each PID.
If you have GNU Parallel installed you can do:
GNU Parallel will give you exit code:
0 - All jobs ran without error.
1-253 - Some of the jobs failed. The exit status gives the number of failed jobs
254 - More than 253 jobs failed.
255 - Other error.
Watch the intro videos to learn more: http://pi.dk/1
Given file names like these:
I hope to get:
Why this doesn’t work?
What’s the right way to do it?
You don’t have to call the external basename
command. Instead, you could use the following commands:
Note that this solution should work in all recent (post 2004) POSIX compliant shells, (e.g. bash
, dash
, ksh
, etc.).
Source: Shell Command Language 2.6.2 Parameter Expansion
More on bash String Manipulations: http://tldp.org/LDP/LG/issue18/bash.html
The basename command has two different invocations; in one, you specify just the path, in which case it gives you the last component, while in the other you also give a suffix that it will remove. So, you can simplify your example code by using the second invocation of basename. Also, be careful to correctly quote things:
fbname=$(basename "$1" .txt) echo "$fbname"
A combination of basename and cut works fine, even in case of double ending like .tar.gz
:
Would be interesting if this solution needs less arithmetic power than Bash Parameter Expansion.
I used to use CShell (csh), which lets you make an alias that takes a parameter. The notation was something like
In Bash, this does not seem to work. Given that Bash has a multitude of useful features, I would assume that this one has been implemented but I am wondering how.
Bash alias does not directly accept parameters. You will have to create a function.
alias
does not accept parameters but a function can be called just like an alias. For example:
myfunction() {
#do things with parameters like $1 such as
mv "$1" "$1.bak"
cp "$2" "$1"
}
myfunction old.conf new.conf #calls `myfunction`
By the way, Bash functions defined in your .bashrc
and other files are available as commands within your shell. So for instance you can call the earlier function like this
Refining the answer above, you can get 1-line syntax like you can for aliases, which is more convenient for ad-hoc definitions in a shell or .bashrc files:
Don’t forget the semi-colon before the closing right-bracket. Similarly, for the actual question:
Or:
The question is simply asked wrong. You don’t make an alias that takes parameters because alias
just adds a second name for something that already exists. The functionality the OP wants is the function
command to create a new function. You do not need to alias the function as the function already has a name.
I think you want something like this :
That’s it! You can use parameters $1, $2, $3, etc, or just stuff them all with $@
I have a need to find all of the writable storage devices attached to a given machine, whether or not they are mounted.
The dopey way to do this would be to try every entry in /dev
that corresponds to a writable devices (hd* and sd*
)……
Is there a better solution, or should I stick with this one?
/proc/partitions
will list all the block devices and partitions that the system recognizes. You can then try using file -s <device>
to determine what kind of filesystem is present on the partition, if any.
You can always do fdisk -l
which seems to work pretty well, even on strange setups such as EC2 xvda devices.
Here is a dump for a m1.large instance:
root@ip-10-126-247-82:~# fdisk -l
Disk /dev/xvda1: 10.7 GB, 10737418240 bytes
255 heads, 63 sectors/track, 1305 cylinders, total 20971520 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
Disk /dev/xvda1 doesn't contain a valid partition table
Disk /dev/xvda2: 365.0 GB, 365041287168 bytes
255 heads, 63 sectors/track, 44380 cylinders, total 712971264 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
Disk /dev/xvda2 doesn't contain a valid partition table
Disk /dev/xvda3: 939 MB, 939524096 bytes
255 heads, 63 sectors/track, 114 cylinders, total 1835008 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
Disk /dev/xvda3 doesn't contain a valid partition table
While mount
says:
root@ip-10-126-247-82:~# mount
/dev/xvda1 on / type ext4 (rw)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
fusectl on /sys/fs/fuse/connections type fusectl (rw)
none on /sys/kernel/debug type debugfs (rw)
none on /sys/kernel/security type securityfs (rw)
udev on /dev type devtmpfs (rw,mode=0755)
devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
none on /run/shm type tmpfs (rw,nosuid,nodev)
/dev/xvda2 on /mnt type ext3 (rw)
And /proc/partitions
says:
root@ip-10-126-247-82:~# cat /proc/partitions
major minor #blocks name
202 1 10485760 xvda1
202 2 356485632 xvda2
202 3 917504 xvda3
How fdisk -l
works is something I would love to know myself.
you can also try lsblk … is in util-linux … but i have a question too
no result
grep sdl /proc/partitions
8 176 15632384 sdl
8 177 15628288 sdl1
lsblk | grep sdl
sdl 8:176 1 14.9G 0 disk
`-sdl1 8:177 1 14.9G 0 part
fdisk is good but not that good … seems like it cannot “see” everything
in my particular example i have a stick that have also a card reader build in it and i can see only the stick using fdisk:
fdisk -l /dev/sdk
Disk /dev/sdk: 15.9 GB, 15931539456 bytes
255 heads, 63 sectors/track, 1936 cylinders, total 31116288 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xbe24be24
Device Boot Start End Blocks Id System
/dev/sdk1 * 8192 31116287 15554048 c W95 FAT32 (LBA)
but not the card (card being /dev/sdl)
also, file -s is inefficient …
file -s /dev/sdl1
/dev/sdl1: sticky x86 boot sector, code offset 0x52, OEM-ID "NTFS ", sectors/cluster 8, reserved sectors 0, Media descriptor 0xf8, heads 255, hidden sectors 8192, dos < 4.0 BootSector (0x0)
that’s nice … BUT
fdisk -l /dev/sdb
/dev/sdb1 2048 156301487 78149720 fd Linux raid autodetect
/dev/sdb2 156301488 160086527 1892520 82 Linux swap / Solaris
file -s /dev/sdb1
/dev/sdb1: sticky \0
to see information about a disk that cannot be accesed by fdisk, you can use parted:
parted /dev/sdl print
Model: Mass Storage Device (scsi)
Disk /dev/sdl: 16.0GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number Start End Size Type File system Flags
1 4194kB 16.0GB 16.0GB primary ntfs
arted /dev/sdb print
Model: ATA Maxtor 6Y080P0 (scsi)
Disk /dev/sdb: 82.0GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number Start End Size Type File system Flags
1 1049kB 80.0GB 80.0GB primary raid
2 80.0GB 82.0GB 1938MB primary linux-swap(v1)
How do you get the length of a string stored in a variable and assign that to another variable?
How do you set another variable to the output 11
?
In addition to fedorqui’s correct answer, I would like to show the difference between string length and byte length:
myvar='Généralités'
chrlen=${#myvar}
oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#myvar}
LANG=$oLang LC_ALL=$oLcAll
printf "%s is %d char len, but %d bytes len.\n" "${myvar}" $chrlen $bytlen
will render:
you could even have a look at stored chars:
myvar='Généralités'
chrlen=${#myvar}
oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#myvar}
printf -v myreal "%q" "$myvar"
LANG=$oLang LC_ALL=$oLcAll
printf "%s has %d chars, %d bytes: (%s).\n" "${myvar}" $chrlen $bytlen "$myreal"
will answer:
Nota: According to Isabell Cowan’s comment, I’ve added setting to $LC_ALL
along with $LANG
.
Argument work same as regular variables
strLen() {
local bytlen sreal oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#1}
printf -v sreal %q "$1"
LANG=$oLang LC_ALL=$oLcAll
printf "String '%s' is %d bytes, but %d chars len: %s.\n" "$1" $bytlen ${#1} "$sreal"
}
will work as
printf
correction tool:
If you:
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
printf " - %-14s is %2d char length\n" "'$string'" ${#string}
done
- 'Généralités' is 11 char length
- 'Language' is 8 char length
- 'Théorème' is 8 char length
- 'Février' is 7 char length
- 'Left: ←' is 7 char length
- 'Yin Yang ☯' is 10 char length
Not really pretty… For this, there is a little function:
strU8DiffLen () {
local bytlen oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#1}
LANG=$oLang LC_ALL=$oLcAll
return $(( bytlen - ${#1} ))
}
Then now:
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
strU8DiffLen "$string"
printf " - %-$((14+$?))s is %2d chars length, but uses %2d bytes\n" \
"'$string'" ${#string} $((${#string}+$?))
done
- 'Généralités' is 11 chars length, but uses 14 bytes
- 'Language' is 8 chars length, but uses 8 bytes
- 'Théorème' is 8 chars length, but uses 10 bytes
- 'Février' is 7 chars length, but uses 8 bytes
- 'Left: ←' is 7 chars length, but uses 9 bytes
- 'Yin Yang ☯' is 10 chars length, but uses 12 bytes
But there left some strange UTF-8 behaviour, like double-spaced chars, zero spaced chars, reverse deplacement and other that could not be as simple… Have a look at diffU8test.sh or diffU8test.sh.txt for more limitations.
To get the length of a string stored in a variable, say:
To confirm it was properly saved, echo
it:
You can use:
wc -c
or wc --bytes
for byte counts = Unicode characters are counted with 2, 3 or more bytes.
wc -m
or wc --chars
for character counts = Unicode characters are counted single until they use more bytes.
I need something simple like date
, but in seconds since 1970 instead of the current date, hours, minutes, and seconds.
date
doesn’t seem to offer that option. Is there an easy way?
This should work:
Just to add.
Get the seconds since epoch(Jan 1 1970) for any given date(e.g Oct 21 1973).
Convert the number of seconds back to date
The command date
is pretty versatile. Another cool thing you can do with date(shamelessly copied from date --help
). Show the local time for 9AM next Friday on the west coast of the US
Better yet, take some time to read the man page http://man7.org/linux/man-pages/man1/date.1.html
So far, all the answers use the external program date
.
Since Bash 4.2, printf
has a new modifier %(dateformat)T
that, when used with argument -1
outputs the current date with format given by dateformat
, handled by strftime(3)
(man 3 strftime
for informations about the formats).
So, for a pure Bash solution:
or if you need to store the result in a variable var
:
No external programs and no subshells!
Since Bash 4.3, it’s even possible to not specify the -1
:
(but it might be wiser to always give the argument -1
nonetheless).
If you use -2
as argument instead of -1
, Bash will use the time the shell was started instead of the current date (but why would you want this?).
I am trying to edit an entry to PATH, as I did something wrong.
I am using Mac OS X 10.10.3
I have tried:
But the file editor opens with nothing inside.
My problem:
I am trying to install ANDROID_HOME to my PATH
I misspelled it, but when I closed the terminal and went back it was gone, so I tried again:
This time, I typed the command correctly but, when I closed the terminal, my settings disappeared again.
How do I execute my desired settings?
If I was to edit bash.profile, how would I enter the above code?
Thanks!
You have to open that file with a text editor and then save it.
It will open the file with TextEdit, paste your things and then save it. If you open it again you’ll find your edits.
You can use other editors:
But if you don’t know how to use them, it’s easier to use the open
approach.
Alternatively, you can rely on pbpaste
. Copy
export ANDROID_HOME=/<installation location>/android-sdk-macosx
export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
in the system clipboard and then in a shell run
Or alternatively you can also use cat
(now cat
waits for input: paste the two export definitions and then hit ctrl-D).
A bit more detailed for beginners:
First, make sure the .bash_profile file is existing? Remember that the .bash_profile file isn’t there by default. You have to create it on your own.
Go into your user folder in finder. The .bash_profile file should be findable there. -> HD/Users/[USERNAME]
Remember: Files with a point at the beginning ‘.’ are hidden by default.
To show hidden files in Mac OS Finder:
If it’s not existing, you have to create .bash_profile on your own.
Open terminal app and switch into user folder with simple command:
If it’s not existing, use this command to create the file:
Second, if you can’t memorise the nerdy commands for save and close in vim, nano etc (the way recommended above) the easiest way to edit is to open .bash_profile file in your favored code editor (Sublime etc.).
Finder -> User folder. Right click -> open with : Sublime Text (or other code editor). Or drag it on app in dock.
… and there you can edit it, pass export commands in new lines.
For beginners: To create your .bash_profile
file in your home directory on MacOS, run:
nano ~/.bash_profile
Then you can paste in the following:
https://gist.github.com/mocon/0baf15e62163a07cb957888559d1b054
As you can see, it includes some example aliases and an environment variable at the bottom.
One you’re done making your changes, follow the instructions at the bottom of the Nano editor window to WriteOut (Ctrl-O
) and Exit (Ctrl-X
). Then quit your Terminal and reopen it, and you will be able to use your newly defined aliases and environment variables.
I’m learning through this tutorial to learn bash scripts to automate a few tasks for me. I’m connecting to a server using putty.
The script, located in .../Documents/LOG
, is:
And I executed the following for read/write/execute permissions
Then, when I enter ./my_script
, I’m getting the error given in the title.
Some similar questions wanted to see these, so I think they might help:
/bin/bash
and
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/bin/mh
I tried adding current directory to PATH
, but that doesn’t work..
I have seen this issue when creating scripts in Windows env and then porting over to run on a Unix environment.
Try running dos2unix
on the script:
http://dos2unix.sourceforge.net/
Or just rewrite the script in your Unix env using vi
and test.
Unix uses different line endings so can’t read the file you created on Windows. Hence it is seeing ^M as an illegal character.
If you want to write a file on Windows and then port over, make sure your editor is set to create files in UNIX format.
In notepad++ in the bottom right of the screen, it tells you the document format. By default, it will say Dos\Windows
. To change it go to
Run following command in terminal
Then try
It should work.
If you use Sublime Text on Windows or Mac to edit your scripts:
Click on View > Line Endings > Unix
and save the file again.
In Bash, what is the simplest way to test if an array contains a certain value?
Edit: With help from the answers and the comments, after some testing, I came up with this:
function contains() {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
echo "contains three"
fi
I’m not sure if it’s the best solution, but it seems to work.
There is sample code that shows how to replace a substring from an array. You can make a copy of the array and try to remove the target value from the copy. If the copy and original are then different, then the target value exists in the original string.
The straightforward (but potentially more time-consuming) solution is to simply iterate through the entire array and check each item individually. This is what I typically do because it is easy to implement and you can wrap it in a function (see this info on passing an array to a function).
This approach has the advantage of not needing to loop over all the elements (at least not explicitly). But since array_to_string_internal()
in array.c still loops over array elements and concatenates them into a string, it’s probably not more efficient than the looping solutions proposed, but it’s more readable.
if [[ " ${array[@]} " =~ " ${value} " ]]; then
# whatever you want to do when arr contains value
fi
if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
# whatever you want to do when arr doesn't contain value
fi
Note that in cases where the value you are searching for is one of the words in an array element with spaces, it will give false positives. For example
The regex will see Jack
as being in the array even though it isn’t. So you’ll have to change IFS
and the separator characters on your regex if you want still to use this solution, like this
Below is a small function for achieving this. The search string is the first argument and the rest are the array elements:
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
A test run of that function could look like:
$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
I often need to kill a process during programming.
The way I do it now is:
[~]$ ps aux | grep 'python csp_build.py'
user 5124 1.0 0.3 214588 13852 pts/4 Sl+ 11:19 0:00 python csp_build.py
user 5373 0.0 0.0 8096 960 pts/6 S+ 11:20 0:00 grep python csp_build.py
[~]$ kill 5124
How can I extract the process id automatically and kill it in the same line?
Like this:
In bash
, you should be able to do:
Details on its workings are as follows:
ps
gives you the list of all the processes.
grep
filters that based on your search string, [p]
is a trick to stop you picking up the actual grep
process itself.
awk
just gives you the second field of each line, which is the PID.
$(x)
construct means to execute x
then take its output and put it on the command line. The output of that ps
pipeline inside that construct above is the list of process IDs so you end up with a command like kill 1234 1122 7654
.
Here’s a transcript showing it in action:
pax> sleep 3600 &
[1] 2225
pax> sleep 3600 &
[2] 2226
pax> sleep 3600 &
[3] 2227
pax> sleep 3600 &
[4] 2228
pax> sleep 3600 &
[5] 2229
pax> kill $(ps aux | grep '[s]leep' | awk '{print $2}')
[5]+ Terminated sleep 3600
[1] Terminated sleep 3600
[2] Terminated sleep 3600
[3]- Terminated sleep 3600
[4]+ Terminated sleep 3600
and you can see it terminating all the sleepers.
Explaining the grep '[p]ython csp_build.py'
bit in a bit more detail:
When you do sleep 3600 &
followed by ps -ef | grep sleep
, you tend to get two processes with sleep
in it, the sleep 3600
and the grep sleep
(because they both have sleep
in them, that’s not rocket science).
However, ps -ef | grep '[s]leep'
won’t create a process with sleep
in it, it instead creates grep '[s]leep'
and here’s the tricky bit: the grep
doesn’t find it because it’s looking for the regular expression "any character from the character class [s]
(which is s
) followed by leep
.
In other words, it’s looking for sleep
but the grep process is grep '[s]leep'
which doesn’t have sleep
in it.
When I was shown this (by someone here on SO), I immediately started using it because
| grep -v grep
; and
if you have pkill,
If you only want to grep against the process name (instead of the full argument list) then leave off -f
.
ps aux | grep -i csp_build | awk '{print $2}' | xargs sudo kill -9
awk '{print $2}'
sudo
is optional
kill -9 5124
, kill -9 5373
etc (kill -15 is more graceful but slightly slower)
I also have 2 shortcut functions defined in my .bash_profile (~/.bash_profile is for osx, you have to see what works for your *nix machine).
p keyword
p csp_build
, p python
etc
bash_profile code:
ka keyword
ka csp_build
, ka python
etc
ka csp_build 15
, ka python 9
bash_profile code:
# KILL ALL
function ka(){
cnt=$( p $1 | wc -l) # total count of processes found
klevel=${2:-15} # kill level, defaults to 15 if argument 2 is empty
echo -e "\nSearching for '$1' -- Found" $cnt "Running Processes .. "
p $1
echo -e '\nTerminating' $cnt 'processes .. '
ps aux | grep -i $1 | grep -v grep | awk '{print $2}' | xargs sudo kill -klevel
echo -e "Done!\n"
echo "Running search again:"
p "$1"
echo -e "\n"
}
How can I determine the current shell I am working on?
Would the output of the ps
command alone be sufficient?
How can this be done in different flavors of UNIX?
There are 3 approaches to finding the name of the current shell’s executable:
Please note that all 3 approaches can be fooled if the executable of the shell is /bin/sh
but it’s really a renamed bash
, for example (which frequently happens).
Thus your second question of whether ps
output will do is answered with “not always”.
echo $0
- will print the program name… which in the case of shell is the actual shell
ps -ef | grep $$ | grep -v grep
- This will look for the current process ID in the list of running processes. Since the current process is shell, it will be included.
ps
listing includes the same number as shell’s process ID, especially if that ID is a small # (e.g. if shell’s PID is “5”, you may find processes called “java5” or “perl5” in the same grep
output!). This is the second problem with the “ps” approach, on top of not being able to rely on the shell name.
echo $SHELL
- The path to the current shell is stored as the SHELL
variable for any shell. The caveat for this one is that if you launch a shell explicitly as a subprocess (e.g. it’s not your login shell), you will get your login shell’s value instead. If that’s a possibility, use the ps
or $0
approach.
If, however, the executable doesn’t match your actual shell (e.g. /bin/sh
is actually bash or ksh), you need heuristics. Here are some environmental variables specific to various shells:
$version
is set on tcsh
$BASH
is set on bash
$shell
(lowercase) is set to actual shell name in csh or tcsh
$ZSH_NAME
is set on zsh
ksh has $PS3
and $PS4
set, whereas normal Bourne shell (sh
) only has $PS1
and $PS2
set. This generally seems like the hardest to distinguish - the ONLY difference in entire set of envionmental variables between sh
and ksh
we have installed on Solaris boxen is $ERRNO
, $FCEDIT
, $LINENO
, $PPID
, $PS3
, $PS4
, $RANDOM
, $SECONDS
, $TMOUT
.
ps -p $$
should work anywhere that the solutions involving ps -ef
and grep
do (on any Unix variant which supports POSIX options for ps
) and will not suffer from the false positives introduced by grepping for a sequence of digits which may appear elsewhere.
Try
or
On my local machine, I run a python script which contains this line
This works fine.
Then I run the same code on a server and I get the following error message
'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import diag
ImportError: No module named swap
So what I did then is I inserted a print bashCommand
which prints me than the command in the terminal before it runs it with os.system()
.
Of course, I get again the error (caused by os.system(bashCommand)
) but before that error it prints the command in the terminal. Then I just copied that output and did a copy paste into the terminal and hit enter and it works…
Does anyone have a clue what’s going on?
Don’t use os.system
. It has been deprecated in favor of subprocess. From the docs: “This module intends to replace several older modules and functions: os.system
, os.spawn
”.
Like in your case:
To somewhat expand on the earlier answers here, there are a number of details which are commonly overlooked.
subprocess.run()
over subprocess.check_call()
and friends over subprocess.call()
over subprocess.Popen()
over os.system()
over os.popen()
text=True
, aka universal_newlines=True
.
shell=True
or shell=False
and how it changes quoting and the availability of shell conveniences.
sh
and Bash
These topics are covered in some more detail below.
subprocess.run()
or subprocess.check_call()
The subprocess.Popen()
function is a low-level workhorse but it is tricky to use correctly and you end up copy/pasting multiple lines of code … which conveniently already exist in the standard library as a set of higher-level wrapper functions for various purposes, which are presented in more detail in the following.
Here’s a paragraph from the documentation:
The recommended approach to invoking subprocesses is to use therun()
function for all use cases it can handle. For more advanced use cases, the underlyingPopen
interface can be used directly.
Unfortunately, the availability of these wrapper functions differs between Python versions.
subprocess.run()
was officially introduced in Python 3.5. It is meant to replace all of the following.
subprocess.check_output()
was introduced in Python 2.7 / 3.1. It is basically equivalent to subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
was introduced in Python 2.5. It is basically equivalent to subprocess.run(..., check=True)
subprocess.call()
was introduced in Python 2.4 in the original subprocess
module (PEP-324). It is basically equivalent to subprocess.run(...).returncode
subprocess.Popen()
The refactored and extended subprocess.run()
is more logical and more versatile than the older legacy functions it replaces. It returns a CompletedProcess
object which has various methods which allow you to retrieve the exit status, the standard output, and a few other results and status indicators from the finished subprocess.
subprocess.run()
is the way to go if you simply need a program to run and return control to Python. For more involved scenarios (background processes, perhaps with interactive I/O with the Python parent program) you still need to use subprocess.Popen()
and take care of all the plumbing yourself. This requires a fairly intricate understanding of all the moving parts and should not be undertaken lightly. The simpler Popen
object represents the (possibly still-running) process which needs to be managed from your code for the remainder of the lifetime of the subprocess.
It should perhaps be emphasized that just subprocess.Popen()
merely creates a process. If you leave it at that, you have a subprocess running concurrently alongside with Python, so a “background” process. If it doesn’t need to do input or output or otherwise coordinate with you, it can do useful work in parallel with your Python program.
os.system()
and os.popen()
Since time eternal (well, since Python 2.5) the os
module documentation has contained the recommendation to prefer subprocess
over os.system()
:
The subprocess
module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function.
The problems with system()
are that it’s obviously system-dependent and doesn’t offer ways to interact with the subprocess. It simply runs, with standard output and standard error outside of Python’s reach. The only information Python receives back is the exit status of the command (zero means success, though the meaning of non-zero values is also somewhat system-dependent).
PEP-324 (which was already mentioned above) contains a more detailed rationale for why os.system
is problematic and how subprocess
attempts to solve those issues.
os.popen()
used to be even more strongly discouraged:
Deprecated since version 2.6: This function is obsolete. Use the subprocess
module.
However, since sometime in Python 3, it has been reimplemented to simply use subprocess
, and redirects to the subprocess.Popen()
documentation for details.
check=True
You’ll also notice that subprocess.call()
has many of the same limitations as os.system()
. In regular use, you should generally check whether the process finished successfully, which subprocess.check_call()
and subprocess.check_output()
do (where the latter also returns the standard output of the finished subprocess). Similarly, you should usually use check=True
with subprocess.run()
unless you specifically need to allow the subprocess to return an error status.
In practice, with check=True
or subprocess.check_*
, Python will throw a CalledProcessError
exception if the subprocess returns a nonzero exit status.
A common error with subprocess.run()
is to omit check=True
and be surprised when downstream code fails if the subprocess failed.
On the other hand, a common problem with check_call()
and check_output()
was that users who blindly used these functions were surprised when the exception was raised e.g. when grep
did not find a match. (You should probably replace grep
with native Python code anyway, as outlined below.)
All things counted, you need to understand how shell commands return an exit code, and under what conditions they will return a non-zero (error) exit code, and make a conscious decision how exactly it should be handled.
text=True
aka universal_newlines=True
Since Python 3, strings internal to Python are Unicode strings. But there is no guarantee that a subprocess generates Unicode output, or strings at all.
(If the differences are not immediately obvious, Ned Batchelder’s Pragmatic Unicode is recommended, if not outright obligatory, reading. There is a 36-minute video presentation behind the link if you prefer, though reading the page yourself will probably take significantly less time.)
Deep down, Python has to fetch a bytes
buffer and interpret it somehow. If it contains a blob of binary data, it shouldn’t be decoded into a Unicode string, because that’s error-prone and bug-inducing behavior - precisely the sort of pesky behavior which riddled many Python 2 scripts, before there was a way to properly distinguish between encoded text and binary data.
With text=True
, you tell Python that you, in fact, expect back textual data in the system’s default encoding, and that it should be decoded into a Python (Unicode) string to the best of Python’s ability (usually UTF-8 on any moderately up to date system, except perhaps Windows?)
If that’s not what you request back, Python will just give you bytes
strings in the stdout
and stderr
strings. Maybe at some later point you do know that they were text strings after all, and you know their encoding. Then, you can decode them.
normal = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True,
text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))
Python 3.7 introduced the shorter and more descriptive and understandable alias text
for the keyword argument which was previously somewhat misleadingly called universal_newlines
.
shell=True
vs shell=False
With shell=True
you pass a single string to your shell, and the shell takes it from there.
With shell=False
you pass a list of arguments to the OS, bypassing the shell.
When you don’t have a shell, you save a process and get rid of a fairly substantial amount of hidden complexity, which may or may not harbor bugs or even security problems.
On the other hand, when you don’t have a shell, you don’t have redirection, wildcard expansion, job control, and a large number of other shell features.
A common mistake is to use shell=True
and then still pass Python a list of tokens, or vice versa. This happens to work in some cases, but is really ill-defined and could break in interesting ways.
# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
# Probably don't forget these, too
check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
shell=True,
# Probably don't forget these, too
check=True, text=True)
The common retort “but it works for me” is not a useful rebuttal unless you understand exactly under what circumstances it could stop working.
Very often, the features of the shell can be replaced with native Python code. Simple Awk or sed
scripts should probably simply be translated to Python instead.
To partially illustrate this, here is a typical but slightly silly example which involves many shell features.
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'round-trip min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
Some things to note here:
shell=False
you don’t need the quoting that the shell requires around strings. Putting quotes anyway is probably an error.
The refactored code also illustrates just how much the shell really does for you with a very terse syntax – for better or for worse. Python says explicit is better than implicit but the Python code is rather verbose and arguably looks more complex than this really is. On the other hand, it offers a number of points where you can grab control in the middle of something else, as trivially exemplified by the enhancement that we can easily include the host name along with the shell command output. (This is by no means challenging to do in the shell, either, but at the expense of yet another diversion and perhaps another process.)
For completeness, here are brief explanations of some of these shell features, and some notes on how they can perhaps be replaced with native Python facilities.
glob.glob()
or very often with simple Python string comparisons like for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash has various other expansion facilities like .{png,jpg}
brace expansion and {1..100}
as well as tilde expansion (~
expands to your home directory, and more generally ~account
to the home directory of another user)
$SHELL
or $my_exported_var
can sometimes simply be replaced with Python variables. Exported shell variables are available as e.g. os.environ['SHELL']
(the meaning of export
is to make the variable available to subprocesses – a variable which is not available to subprocesses will obviously not be available to Python running as a subprocess of the shell, or vice versa. The env=
keyword argument to subprocess
methods allows you to define the environment of the subprocess as a dictionary, so that’s one way to make a Python variable visible to a subprocess). With shell=False
you will need to understand how to remove any quotes; for example, cd "$HOME"
is equivalent to os.chdir(os.environ['HOME'])
without quotes around the directory name. (Very often cd
is not useful or necessary anyway, and many beginners omit the double quotes around the variable and get away with it until one day …)
grep 'foo' <inputfile >outputfile
opens outputfile
for writing and inputfile
for reading, and passes its contents as standard input to grep
, whose standard output then lands in outputfile
. This is not generally hard to replace with native Python code.
echo foo | nl
runs two subprocesses, where the standard output of echo
is the standard input of nl
(on the OS level, in Unix-like systems, this is a single file handle). If you cannot replace one or both ends of the pipeline with native Python code, perhaps think about using a shell after all, especially if the pipeline has more than two or three processes (though look at the pipes
module in the Python standard library or a number of more modern and versatile third-party competitors).
sh
and Bash
subprocess
runs your shell commands with /bin/sh
unless you specifically request otherwise (except of course on Windows, where it uses the value of the COMSPEC
variable). This means that various Bash-only features like arrays, [[
etc are not available.
If you need to use Bash-only syntax, you can pass in the path to the shell as executable='/bin/bash'
(where of course if your Bash is installed somewhere else, you need to adjust the path).
subprocess.run('''
# This for loop syntax is Bash only
for((i=1;i<=$#;i++)); do
# Arrays are Bash-only
array[i]+=123
done''',
shell=True, check=True,
executable='/bin/bash')
subprocess
is separate from its parent, and cannot change it
A somewhat common mistake is doing something like
which aside from the lack of elegance also betrays a fundamental lack of understanding of the “sub” part of the name “subprocess”.
A child process runs completely separate from Python, and when it finishes, Python has no idea what it did (apart from the vague indicators that it can infer from the exit status and output from the child process). A child generally cannot change the parent’s environment; it cannot set a variable, change the working directory, or, in so many words, communicate with its parent without cooperation from the parent.
The immediate fix in this particular case is to run both commands in a single subprocess;
though obviously this particular use case doesn’t require the shell at all. Remember, you can manipulate the environment of the current process (and thus also its children) via
or pass an environment setting to a child process with
(not to mention the obvious refactoring subprocess.run(['echo', 'bar'])
; but echo
is a poor example of something to run in a subprocess in the first place, of course).
This is slightly dubious advice; there are certainly situations where it does make sense or is even an absolute requirement to run the Python interpreter as a subprocess from a Python script. But very frequently, the correct approach is simply to import
the other Python module into your calling script and call its functions directly.
If the other Python script is under your control, and it isn’t a module, consider turning it into one. (This answer is too long already so I will not delve into details here.)
If you need parallelism, you can run Python functions in subprocesses with the multiprocessing
module. There is also threading
which runs multiple tasks in a single process (which is more lightweight and gives you more control, but also more constrained in that threads within a process are tightly coupled, and bound to a single GIL.)
Is it possible to pipe to/from the clipboard in Bash?
Whether it is piping to/from a device handle or using an auxiliary application, I can’t find anything.
For example, if /dev/clip
was a device linking to the clipboard we could do:
There’s a wealth of clipboards you could be dealing with. I expect you’re probably a Linux user who wants to put stuff in the X Windows primary clipboard. Usually, the clipboard you want to talk to has a utility that lets you talk to it.
In the case of X, there’s xclip
(and others). xclip -selection c
will send data to the clipboard that works with Ctrl + C, Ctrl + V in most applications.
If you’re on Mac OS X, there’s pbcopy
.
If you’re in Linux terminal mode (no X) then look into gpm
or screen which has a clipboard. Try the screen
command readreg
.
Under Windows 10+ or cygwin, use /dev/clipboard
or clip
.
Make sure you are using alias xclip="xclip -selection c"
otherwise you can’t just use to Ctrl+v to paste it back in a different place.
Ctrl+v === test
If you do not have access to apt-get
nor pacman
, nor dnf
, the sources are available on sourceforge.
In ~/.bash_aliases
, add:
Do not forget to load your new configuration using . ~/.bash_aliases
or by restarting your profile.
In ~/.config/fish/config.fish
, add:
Do not forget to restart your fish instance by restarting your terminal for changes to apply.
You can now use setclip
and getclip
, e.g:
I found some ways to pass external shell variables to an awk
script, but I’m confused about '
and "
.
First, I tried with a shell script:
Then tried awk:
Why is the difference?
Lastly I tried this:
$ awk 'BEGIN{print " '$v' "}'
$ 123test
$ awk 'BEGIN{print ' "$v" '}'
awk: cmd. line:1: BEGIN{print
awk: cmd. line:1: ^ unexpected newline or end of string
I’m confused about this.
Getting shell variables into
awk
may be done in several ways. Some are better than others. This should cover most of them. If you have a comment, please leave below.
-v
(The best way, most portable)
Use the -v
option: (P.S. use a space after -v
or it will be less portable. E.g., awk -v var=
not awk -vvar=
)
This should be compatible with most awk
, and the variable is available in the BEGIN
block as well:
If you have multiple variables:
Warning. As Ed Morton writes, escape sequences will be interpreted so \t
becomes a real tab
and not \t
if that is what you search for. Can be solved by using ENVIRON[]
or access it via ARGV[]
PS If you like three vertical bar as separator |||
, it can’t be escaped, so use -F"[|][|][|]"
Example on getting data from a program/function inn to awk
(here date is used)
Here we get the variable after the awk
code. This will work fine as long as you do not need the variable in the BEGIN
block:
variable="line one\nline two"
echo "input data" | awk '{print var}' var="${variable}"
or
awk '{print var}' var="${variable}" file
This also works with multiple variables awk '{print a,b,$0}' a="$var1" b="$var2" file
Using variable this way does not work in BEGIN
block:
Variable can also be added to awk
using a here-string from shells that support them (including Bash):
This is the same as:
P.S. this treats the variable as a file input.
ENVIRON
input
As TrueY writes, you can use the ENVIRON
to print Environment Variables. Setting a variable before running AWK, you can print it out like this:
ARGV
input
As Steven Penny writes, you can use ARGV
to get the data into awk:
To get the data into the code itself, not just the BEGIN:
You can use a variable within the awk
code, but it’s messy and hard to read, and as Charles Duffy
points out, this version may also be a victim of code injection. If someone adds bad stuff to the variable, it will be executed as part of the awk
code.
This works by extracting the variable within the code, so it becomes a part of it.
If you want to make an awk
that changes dynamically with use of variables, you can do it this way, but DO NOT use it for normal variables.
Here is an example of code injection:
variable='line one\nline two" ; for (i=1;i<=1000;++i) print i"'
awk 'BEGIN {print "'"$variable"'"}'
line one
line two
1
2
3
.
.
1000
You can add lots of commands to awk
this way. Even make it crash with non valid commands.
It’s always good to double quote variable "$variable"
If not, multiple lines will be added as a long single line.
Example:
var="Line one
This is line two"
echo $var
Line one This is line two
echo "$var"
Line one
This is line two
Other errors you can get without double quote:
variable="line one\nline two"
awk -v var=$variable 'BEGIN {print var}'
awk: cmd. line:1: one\nline
awk: cmd. line:1: ^ backslash not last character on line
awk: cmd. line:1: one\nline
awk: cmd. line:1: ^ syntax error
And with single quote, it does not expand the value of the variable:
It seems that the good-old ENVIRON
awk built-in hash is not mentioned at all. An example of its usage:
Use either of these depending how you want backslashes in the shell variables handled (avar
is an awk variable, svar
is a shell variable):
awk -v avar="$svar" '... avar ...' file
awk 'BEGIN{avar=ARGV[1];ARGV[1]=""}... avar ...' "$svar" file
See http://cfajohnson.com/shell/cus-faq-2.html#Q24 for details and other options. The first method above is almost always your best option and has the most obvious semantics.
I would like to have the echo
command executed when cat /etc/passwd | grep "sysa"
is not true.
What am I doing wrong?
try
grep
returns true
if it finds the search target, and false
if it doesn’t.
So NOT false
== true
.
if
evaluation in shells are designed to be very flexible, and many times doesn’t require chains of commands (as you have written).
Also, looking at your code as is, your use of the $( ... )
form of cmd-substitution is to be commended, but think about what is coming out of the process. Try echo $(cat /etc/passwd | grep "sysa")
to see what I mean. You can take that further by using the -c
(count) option to grep and then do if ! [ $(grep -c "sysa" /etc/passwd) -eq 0 ] ; then
which works but is rather old school.
BUT, you could use the newest shell features (arithmetic evaluation) like
which also gives you the benefit of using the c-lang based comparison operators, ==,<,>,>=,<=,%
and maybe a few others.
In this case, per a comment by Orwellophile, the arithmetic evaluation can be pared down even further, like
OR
Finally, there is an award called the Useless Use of Cat (UUOC)
. :-) Some people will jump up and down and cry gothca! I’ll just say that grep
can take a file name on its cmd-line, so why invoke extra processes and pipe constructions when you don’t have to? ;-)
I hope this helps.
I think it can be simplified into:
or in a single command line
$ grep sysa /etc/passwd || { echo "ERROR - The user sysa could not be looked up"; exit 2; }
What am I doing wrong?
$(...)
holds the value, not the exit status, that is why this approach is wrong. However, in this specific case, it does indeed work because sysa
will be printed which makes the test statement come true. However, if ! [ $(true) ]; then echo false; fi
would always print false
because the true
command does not write anything to stdout (even though the exit code is 0). That is why it needs to be rephrased to if ! grep ...; then
.
An alternative would be cat /etc/passwd | grep "sysa" || echo error
. Edit: As Alex pointed out, cat is useless here: grep "sysa" /etc/passwd || echo error
.
Found the other answers rather confusing, hope this helps someone.
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.
That’s simple. It’s explained in man bash
:
/bin/bash
The bash executable
/etc/profile
The systemwide initialization file, executed for login shells
~/.bash_profile
The personal initialization file, executed for login shells
~/.bashrc
The individual per-interactive-shell startup file
~/.bash_logout
The individual login shell cleanup file, executed when a login shell exits
~/.inputrc
Individual readline initialization file
Login shells are the ones that are read 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.
~/.environment
file is a compatibility file for Korn Shell.