Fixing invalid number of lines in tail by passing an integer into tail - linux

Running Centos8.
Here is my current (tiny) script:
#!/usr/bin/env bash
x=$((($2-$1)+1))
head -n $2 $3 | tail -n -$x
As an example of an input:
sh script.sh 7 10 /etc/passwd
And what I am trying to get out is lines 7-10 from /etc/passwd.
It should be fairly simple, because just doing
head -n 7 /etc/passwd | tail -n -4
Returns those 4 lines perfectly fine.
However, using $x (to get the input from the second number, minus the first, plus 1), fails. Whenever I run this code I get this error:
tail: invalid number of lines: ‘4\r\r’
Which from my understanding means that the code is not accepting the $x input from the small math I did, and thinks that its 4 with two trailing spaces?
How would I go about fixing this issue, so that the four lines of /etc/password get listed?

There's problem on line 3
#!/usr/bin/env bash
x=$((($2-$1)+1))
head -n $1 $3 | tail -n -$x
That needs to say $1, not $2.
And the line endings are Mac, not Linux.

Related

Bash script and manually running commands on the command line

I have the following simple bash script which takes input from stdin and prints the third line given as input.
#!/bin/bash
var=$(cat)
echo $var | head -n 3 | tail -n 1
The problem with this script is that it prints all the lines but here is the funny part, when I type the commands individually on the command line I am getting the expected result i.e. the third line. Why this anomaly? Am I doing something wrong here?
The aim of head -n 3 | tail -n 1 is to keep the third line into variable
It will be more efficient to use read builtin
read
read
read var
echo "${var}"
Or to keep heading white-spaces
IFS= read
and not join lines ending with \ or not give special meaning to \
read -r
You don't need $(cat) in your script. If script is reading data from stdin then just have this single line in your script:
head -n 3 | tail -n 1
And run it as:
bash myscript.sh < file.txt
This will print 3rd line from file.txt
PS: You can replace head + tail with this faster sed to print 3rd line from input:
sed '3q;d'
The shell is splitting the var variable so echo get multiple parameters. You need to quote your variable to prevent this to happen:
#!/bin/bash
var=$(cat)
echo "$var" | head -n 3 | tail -n 1
This should do the trick, as far as I understand your question:
#!/bin/bash
var=$(cat)
echo "$var" | head -n 3 | tail -n 1
var=$(cat) will not allow you to escape out of stdin mode. you need to specify the EOF for the script to understand to stop reading from stdin.
read -d '' var << EOF
echo "$var" | head -n 3 | tail -n 1

Getting error "cat: write error: Broken pipe" only when running bash script non-interactively

I wrote a bash script where I define a variable like this:
var=$(cat $file_path | head -n $var2 | tail -n 1 | cut -f1)
Where $file_path simply contains the path to a file and $var2 is an int, e.g., 1 or 2. The variable is therefore assigned the value of the first field of line number var2 of the file.
It works perfectly fine when I run this from the command line. However, when running the script containing this command, I get the error
cat: write error: Broken pipe
Any idea why that is?
There's no need to use cat, since head takes a filename argument.
var=$(head -n $var2 $file_path | tail -n 1 | cut -f1)
Actually, there's no need to use any of those commands.
var=$(awk -v line=$var2 'NR == line { print $1; exit }' $file_path)

i'm trying to write a bash script and i don't get the result displayed in the terminal

i'm trying to display the 3rd to the 7th line from a file but i get nothing displayed in the terminal i'm using this command :
head -n 7 /etc/passwd | tail -n +3
i want the result be seen in the terminal .
You can try this way
head -n 7 /etc/passwd | tail -n 5
For example :
seq 20 | head -n 7 | tail -n 5
Output :
3
4
5
6
7
Explanation :
head -n 7 -- print the first 7 lines ( so 1..7 printed)
tail -n 5 -- print last 5 lines ( so skipped first two lines 3..7 printed )
head -n 7 /etc/passwd | tail -n +3
Seems to do exactly what you want it to do for me. Have you verified the contents of /etc/passwd? If you do not have permission to read the file or it is empty you will get no output.
I would check other places in the script. Change the first line of your script to:
#!/bin/bash -v
to have it echo the commands so you can make sure what you think is being executed actually is.
What do you get than ? Show the file output, but choose another file ;)
It sometimes helps to just change commands, try with either HEAD or TAIL twice, to come to the same result.

Get the last 4 characters of output from standard out

I have a script that is running and uses
lspci -s 0a.00.1
This returns
0a.00.1 usb controller some text device 4dc9
I want to get those last 4 characters inline such that
lspci -s 0a.00.1 | some command to give me the last 4 characters.
How about tail, with the -c switch. For example, to get the last 4 characters of "hello":
echo "hello" | tail -c 5
ello
Note that I used 5 (4+1) because a newline character is added by echo. As suggested by Brad Koch below, use echo -n to prevent the newline character from being added.
Do you really want the last four characters? It looks like you want the last "word" on the line:
awk '{ print $NF }'
This will work if the ID is 3 characters, or 5, as well.
Using sed:
lspci -s 0a.00.1 | sed 's/^.*\(.\{4\}\)$/\1/'
Output:
4dc9
Try this, say if the string is stored in the variable foo.
foo=`lspci -s 0a.00.1` # the foo value should be "0a.00.1 usb controller some text device 4dc9"
echo ${foo:(-4)} # which should output 4dc9
I usually use
echo 0a.00.1 usb controller some text device 4dc9 | rev | cut -b1-4 | rev
4dc9
If the real request is to copy the last space-separated string regardless of its length, then the best solution seems to be using ... | awk '{print $NF}' as given by #Johnsyweb. But if this is indeed about copying a fixed number of characters from the end of a string, then there is a bash-specific solution without the need to invoke any further subprocess by piping:
$ test="1234567890"; echo "${test: -4}"
7890
$
Please note that the space between colon and minus character is essential, as without it the full string will be delivered:
$ test="1234567890"; echo "${test:-4}"
1234567890
$
Try using grep:
lspci -s 0a.00.1 | grep -o ....$
This will print last 4 characters of every line.
However if you'd like to have last 4 characters of the whole output, use tail -c4 instead.
One more way to approach this is to use <<< notation:
tail -c 5 <<< '0a.00.1 usb controller some text device 4dc9'
instead of using named variables, develop the practice of using the positional parameters, like this:
set -- $( lspci -s 0a.00.1 ); # then the bash string usage:
echo ${1:(-4)} # has the advantage of allowing N PP's to be set, eg:
set -- $(ls *.txt)
echo $4 # prints the 4th txt file.

Print a file, skipping the first X lines, in Bash [duplicate]

This question already has answers here:
How can I remove the first line of a text file using bash/sed script?
(19 answers)
Closed 3 years ago.
I have a very long file which I want to print, skipping the first 1,000,000 lines, for example.
I looked into the cat man page, but I did not see any option to do this. I am looking for a command to do this or a simple Bash program.
You'll need tail. Some examples:
$ tail great-big-file.log
< Last 10 lines of great-big-file.log >
If you really need to SKIP a particular number of "first" lines, use
$ tail -n +<N+1> <filename>
< filename, excluding first N lines. >
That is, if you want to skip N lines, you start printing line N+1. Example:
$ tail -n +11 /tmp/myfile
< /tmp/myfile, starting at line 11, or skipping the first 10 lines. >
If you want to just see the last so many lines, omit the "+":
$ tail -n <N> <filename>
< last N lines of file. >
Easiest way I found to remove the first ten lines of a file:
$ sed 1,10d file.txt
In the general case where X is the number of initial lines to delete, credit to commenters and editors for this:
$ sed 1,Xd file.txt
If you have GNU tail available on your system, you can do the following:
tail -n +1000001 huge-file.log
It's the + character that does what you want. To quote from the man page:
If the first character of K (the number of bytes or lines) is a
`+', print beginning with the Kth item from the start of each file.
Thus, as noted in the comment, putting +1000001 starts printing with the first item after the first 1,000,000 lines.
If you want to skip first two line:
tail -n +3 <filename>
If you want to skip first x line:
tail -n +$((x+1)) <filename>
A less verbose version with AWK:
awk 'NR > 1e6' myfile.txt
But I would recommend using integer numbers.
Use the sed delete command with a range address. For example:
sed 1,100d file.txt # Print file.txt omitting lines 1-100.
Alternatively, if you want to only print a known range, use the print command with the -n flag:
sed -n 201,300p file.txt # Print lines 201-300 from file.txt
This solution should work reliably on all Unix systems, regardless of the presence of GNU utilities.
Use:
sed -n '1d;p'
This command will delete the first line and print the rest.
If you want to see the first 10 lines you can use sed as below:
sed -n '1,10 p' myFile.txt
Or if you want to see lines from 20 to 30 you can use:
sed -n '20,30 p' myFile.txt
Just to propose a sed alternative. :) To skip first one million lines, try |sed '1,1000000d'.
Example:
$ perl -wle 'print for (1..1_000_005)'|sed '1,1000000d'
1000001
1000002
1000003
1000004
1000005
You can do this using the head and tail commands:
head -n <num> | tail -n <lines to print>
where num is 1e6 + the number of lines you want to print.
This shell script works fine for me:
#!/bin/bash
awk -v initial_line=$1 -v end_line=$2 '{
if (NR >= initial_line && NR <= end_line)
print $0
}' $3
Used with this sample file (file.txt):
one
two
three
four
five
six
The command (it will extract from second to fourth line in the file):
edu#debian5:~$./script.sh 2 4 file.txt
Output of this command:
two
three
four
Of course, you can improve it, for example by testing that all argument values are the expected :-)
cat < File > | awk '{if(NR > 6) print $0}'
I needed to do the same and found this thread.
I tried "tail -n +, but it just printed everything.
The more +lines worked nicely on the prompt, but it turned out it behaved totally different when run in headless mode (cronjob).
I finally wrote this myself:
skip=5
FILE="/tmp/filetoprint"
tail -n$((`cat "${FILE}" | wc -l` - skip)) "${FILE}"

Resources