How to show line number when executing bash script
You mention that you're already using -x
. The variable PS4
denotes the value is the prompt printed before the command line is echoed when the -x
option is set and defaults to :
followed by space.
You can change PS4
to emit the LINENO
(The line number in the script or shell function currently executing).
For example, if your script reads:
$ cat script
foo=10
echo ${foo}
echo $((2 + 2))
Executing it thus would print line numbers:
$ PS4='Line ${LINENO}: ' bash -x script
Line 1: foo=10
Line 2: echo 10
10
Line 3: echo 4
4
http://wiki.bash-hackers.org/scripting/debuggingtips gives the ultimate PS4
that would output everything you will possibly need for tracing:
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
How to show the line number in Shell (/bin/sh) script when debugging (-x)?
All the functionality discussed here is already required in the User Portability Utilities annex to the POSIX standard.
Moreover, dash, the most common non-bash /bin/sh
implementation on Linux, already has the functionality built-in, as you can test below:
dash -s <<'EOF'
PS4=':$LINENO+'; set -x
echo "First line"
echo "Second line"
EOF
...correctly emits (with dash 0.5.10.2):
:2+echo First line
First line
:3+echo Second line
Second line
While executing shell scripts, how to know which line number it's executing,
You can set the PS4
variable to cause set -x
output to include the line number:
PS4=':${LINENO}+'
set -x
This will put the line number before each line as it executes:
:4+command here
:5+other command
It's important to have some sigil character (such as a +
in my examples) after your variable expansions in PS4
, because that last character is repeated to show nesting depth. That is, if you call a function, and that function invokes a command, the output from set -x
will report it like so:
:3+++command run within a function called from a function
:8++command run within a function
:19+line after the function was called
If multiple files are involved in running your script, you might want to include the BASH_SOURCE
variable as opposed to only LINENO
(assuming this really is a bash script, as opposed to /bin/sh
-- be sure your script starts with #!/bin/bash
!):
PS4=':${BASH_SOURCE}:${LINENO}+'
set -x
Getting the line number of a shell script error
To add line numbers to bash -x
output:
PS4='$LINENO:' bash -x script
For complex cases where scripts call one another, it can be handy to know not just the line number but also the file name:
PS4='$BASH_SOURCE:$LINENO:' bash -x script
PS4
can be further customized as you please. For example:
PS4='File=$BASH_SOURCE: LineNo=$LINENO: ' bash -x script
Filtering the output
If we know what error message we are looking for, we can filter the output to get just that message and its corresponding file name and line number:
$ PS4='File=$BASH_SOURCE: LineNo=$LINENO: ' bash -x script 2>&1 | grep -B1 'awk: fatal:'
File=script: LineNo=3: awk 1 text.txt
awk: fatal: cannot open file `text.txt' for reading (No such file or directory)
Bash logging including line number
Use $LINENO: Each time this parameter is referenced, the shell substitutes a decimal number representing the current sequential line number (starting with 1) within a script or function.
Shell script print line number when it errors out
Using
PS4=':$LINENO+'
will add line number to the output of set -x
.
If you only want to print that on errors, there's some risk of running into bugs in recent interpreters. However, you can try the following (first given in this previous answer):
error() {
local parent_lineno="$1"
local message="$2"
local code="${3:-1}"
if [[ -n "$message" ]] ; then
echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
else
echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
fi
exit "${code}"
}
trap 'error ${LINENO}' ERR
Again, this will not work on some recent builds of bash, which don't always have LINENO
set correctly inside traps.
Another approach (which will only work on recent shells; the below uses some bash 4.0 and 4.1 features) is to use PS4
to emit the exit status and line number of each command to a dedicated file descriptor, and use tail
to print only the last line given to that FD before the shell exits:
exec {BASH_XTRACEFD}> >(tail -n 1) # send set -x output to tail -n 1
PS4=':At line $LINENO; prior command exit status $?+'
set -x
How to show line number when executing csh script?
There is no built-in feature to do this. The only method I can think of is (ab)using the postcmd
special alias. This alias will be run after every command:
set _lineno = 0
alias postcmd '@ _lineno++ && echo -n $_lineno\ '
echo a
echo b
echo c && echo d
Outputs:
% csh test.csh
1 2 a
3 b
4 c
d
Unfortunately, this will clutter the -x
output:
% csh -x test.csh
set _lineno = 0
alias postcmd @ _lineno++ && echo -n $_lineno\
@ _lineno++
echo -n 1
1 @ _lineno++
echo -n 2
2 echo a
a
@ _lineno++
echo -n 3
3 echo b
b
@ _lineno++
echo -n 4
4 echo c
c
echo d
d
Which we can sort of filter with grep
:
% csh -x test.tcsh | & grep -Ev '^(@ _lineno|echo -n [[:digit:]]+)
set _lineno = 0
alias postcmd @ _lineno++ && echo -n $_lineno\
1 @ _lineno++
2 echo a
a
3 echo b
b
4 echo c
c
echo d
d
All of this is less than perfect, and may not even work very well with large scripts...
As you've asked a number of csh
related questions for the last few days:
While tcsh
can work fine as an interactive shell, tcsh
or csh
is generally not well suited for scripting tasks. There are many things missing, incomplete, or simply behave in a "strange" way. It's even more ugly than normal shell scripting with POSIX (and that's saying a lot).
Unless you have a good reason not to (such as maintaining existing scripts), you probably should give up on csh
for now, and go with either a "real" programming language (Python, Ruby, Perl, whatever), or use /bin/sh
, bash
, or zsh
.
How to get the line number of a match?
To print the line number of your match, use the -n
option of grep
. Since the pattern contains some special characters, use -F
to make them be interpreted as fixed strings and not a regular expression:
grep -Fn 'your_line' /etc/crontab
However, since you want to print some message together with the line number, you may want to use awk
instead:
awk -v line='your_line' '$0 == line {print "this is the line number", NR, "from", FILENAME}' /etc/crontab
Test
$ cat a
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
#
0 17 * * * Me echo "end of work"
0 8 * * * Me echo "start working please"
1 3 2 4 2 Me ls -la
With awk
:
$ awk -v line='0 8 * * * Me echo "start working please"' '$0 == line {print "this is the line number", NR, "from", FILENAME}' a
this is the line number 13 from a
With grep
:
$ grep -Fn '0 8 * * * Me echo "start working please"' a13:0 8 * * * Me echo "start working please"
13:0 8 * * * Me echo "start working please"
How to print the line number where a string appears in a file?
Using grep
To look for word
in file and print the line number, use the -n
option to grep
:
grep -n 'word' file
This prints both the line number and the line on which it matches.
Using awk
This will print the number of line on which the word word
appears in the file:
awk '/word/{print NR}' file
This will print both the line number and the line on which word
appears:
awk '/word/{print NR, $0}' file
You can replace word
with any regular expression that you like.
How it works:
/word/
This selects lines containing
word
.{print NR}
For the selected lines, this prints the line number (NR means Number of the Record). You can change this to print any information that you are interested in. Thus,
{print NR, $0}
would print the line number followed by the line itself,$0
.
Assigning the line number to a variable
Use command substitution:
n=$(awk '/word/{print NR}' file)
Using shell variables as the pattern
Suppose that the regex that we are looking for is in the shell variable url
:
awk -v x="$url" '$0~x {print NR}' file
And:
n=$(awk -v x="$url" '$0~x {print NR}' file)
Related Topics
Centos Error - Sudo: Effective Uid Is Not 0, Is Sudo Installed Setuid Root
Get Gnu Octave to Work with a Multicore Processor. (Multithreading)
How to Find Which Elf Dependency Is Not Fulfilled
How Does a Linux Socket Buffer Overflow
Swift on Os X Compiling for Linux
How to Use Expect with Optional Prompts
How to Fix Permission Denied for .Git/ Directory When Performing Git Push
Check If Rsync Command Ran Successful
Bash VS Csh VS Others - Which Is Better for Application Maintenance
How to Convert String to Number in Gnuplot
Setting an Environment Variable Through a Perl Script
Doesn't Sh Support Process Substitution <(...)
Linux Kernel System Call Returns -1 Instead of {-1, -256}
What Sort Order Does Linux Use
In Order to Write Pci Ethernet Driver. How to Implement Mmap in the Pci Ethernet Driver
What Should Linux/Unix 'Make Install' Consist Of