How to Show Line Number When Executing Bash Script

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



Leave a reply



Submit