Print specific line number only if match in sed - shell

How do you print a (specific) line only if there is a match in sed (Linux stream editor)? Let's say I have line three that I would like to print only if it meets the match criteria; how can I print this?
I am piping a command's output to sed, and would prefer not to use sed's output to pipe to sed again:
| sed -ne ''"$currline"'p' | sed -n '/state/p'`
Also, I was assigning the output to a variable with backticks.
Given inputs A and B, and the search pattern state, the output for A should be the line 3 stateless (note that 3 is part of the data), and for B should be nothing:
Input A Input B
1 state 1 state
2 statement 2 statement
3 stateless 3 statless
4 stated 4 stated
5 estate 5 estate

sed -n '3{/state/p;}' $file
The 3 matches line 3; the actions on line 3 are 'if you find /state/, print'; the -n prevents general printing of lines.
Also, you should avoid backticks; it is better to use the var=$(cmd1 | cmd2) notation than var=`cmd1 | cmd2` notation.

As I understand you want to match two criterias, a specific line number an a pattern. You can achieve it in one sed command.
Assuming infile with content:
one
two
three
four
five
And if you want to print the fourth line if it matches ur, use:
sed -ne '4 { /ur/ { p; q } }' infile
That yields:
four

sed is an excellent tool for simple substitutions on a single line but for anything else just use awk:
awk 'NR==3 && /state/'

With awk :
awk 'NR==3 && /pattern/{print;exit}'
NOTE
pattern is a regex
exit avoid to parse the whole file

This might work for you (GNU sed):
sed '3!d;/state/q;Q' file
or
sed -ne '3!b' -e '/state/p' -e 'q' file

Related

printing only specific lines with sed

I have following File (wishlist.txt):
Alligatoah Musik_ist_keine_lösung;https:///uhfhf
Alligatoah STRW;https:///uhfhf?i
Amewu Entwicklungshilfe;https:///uhfhf?i
and want to have the first word of line n.
so for n = 1:
Alligatoah
What i have so far is:
sed -e 's/\s.*//g' wishlist.txt
is there a elegant way to get rid of all lines except n?
Edit:
How to pass a bash variable "$i" to sed since
sed -n '$is/ .*//p' $wishlist
and
sed -n "\`${i}\`s/ .*//p" $wishlist
doesn't work
A couple of other techniques to get the first word of the 3rd line:
awk -v line=3 'NR == line {print $1; exit}' file
or
head -n 3 file | tail -n 1 | cut -d ' ' -f 1
Something like this. For the 1st word of the 3rd line.
sed -n '3s/\s.*//p' wishlist.txt
To use a variable: Note: Double quotes.
line=3; sed -n "${line}s/\s.*//p" wishlist.txt
sed supports "addresses", so you can tell it what lines to operate on. To print only the first line, you can use
sed -e '1!d; s/\s.*//'
where 1!d means: on lines other then 1, delete the line.

sed find and replace a specific number [duplicate]

This question already has answers here:
sed whole word search and replace
(5 answers)
Closed 4 years ago.
I have a file like following.
abc 259200000 2 3 864000000 3 5
def 86400000 2 62 864000000 3 62
efg 864000000 2 347 0 0 0
abcd 259200000 3 3 0 0 0
I need to replace any single 0 with word Not Exist. I tried following and none of them are working.
sed 's/[0]/Not Exist/g' data.txt > out.txt
sed 's/[^0]/Not Exist/g' data.txt > out.txt
sed 's/^[0]/Not Exist/g' data.txt > out.txt
Much appreciate any help.
Could you please try following if ok with awk.
awk '{for(i=1;i<=NF;i++){if($i==0){$i="Not Exist"}}}{$1=$1} 1' OFS="\t" Input_file
Adding a non-one liner form of solution too now.
awk '
{
for(i=1;i<=NF;i++){
if($i==0){
$i="Not Exist"
}
}
}
{
$1=$1
}
1
' OFS="\t" Input_file
Explanation: Adding explanation for above code too now.
awk '
{
for(i=1;i<=NF;i++){ ##Starting for loop from variable i=1 to value of NF(number of field) increment with 1 each time.
if($i==0){ ##Checking condition if value of field is 0 then do following.
$i="Not Exist" ##Re-making value of that field to string Not Exist now.
} ##Closing if condition block now.
} ##Closing for loop block here.
}
{
$1=$1 ##re-setting first field on current line(to make sure TAB is being made output field separator to edited lines).
}
1 ##Mentioning 1 means awk works on method on pattern and action. So making condition/pattern as TRUE and not mentioning any action so by default print of current line will happen.
' OFS="\t" Input_file ##Setting OFS as TAB and mentioning Input_file name here.
Here's why your three attempts so far don't work:
sed 's/[0]/Not Exist/g' data.txt > out.txt
This asks sed to replace any zero character with the replacement string, including those that are part of a larger number.
sed 's/[^0]/Not Exist/g' data.txt > out.txt
This asks sed to replace any character which is NOT zero with the replacement string. The ^ "negates" the regex bracket expression.
sed 's/^[0]/Not Exist/g' data.txt > out.txt
This asks sed to replace any zero that is at the beginning of the line, since the ^ means "the null at the beginning of the line" in this context.
What you're looking for is might be expressed as follows:
sed 's/\([[:space:]]\)0\([[:space:]]\)/\1Not exist\2/g; s/\([[:space:]]\)0$/\1Not exist/' data.txt > out.txt
In this solution I'm using the space character class since I don't know whether your input file is tab or space separated. The class works with both, and retains whatever was there before.
Note that there are two sed commands here -- the first processes zeros that are have text after them, and the second processes zeros that at are the end of the line. This does make the script a bit awkward, so if you're on a more modern operating system with a sed that includes a -E option, the following might be easier to read:
sed -E 's/([[:space:]])0([[:space:]]|$)/\1Not exist\2/g' data.txt > out.txt
This takes advantage of the fact that in ERE, an "atom" can have multiple "branches", separated by an or bar (|). For more on this, man re_format.
Note that sed is probably not the best tool for this. Processing fields is usually best done with awk. I can't improve on #RavinderSingh13's awk solution, so you should use that if awk is an option.
Of course, your formatting is going to be wonky with almost any option.
I assume the columns are separated by white-space characters, then:
When using sed, you need to search for a lonely zero, that is zero "enclosed" in spaces. So you need to check the char after and before zero if it is equal to space. Also you need to handle the first zero and the last zero on the line separately.
sed '
# replace 0 beeing the first character on the line
s/^0\([[:space:]]\)/Not Exists\1/
# replace zeros separated by spaces
s/\([[:space:]]\)0\([[:space:]]\)/\1Not Exists\2/g
# replace the last 0
s/\([[:space:]]\)0&/\1Not Exists/ ' data.txt > out.txt
Live example at tutorialpoint.
Using sed:
sed 's/\<0\>/NotExist/g' file | column -t
\<...\> matches a word.
column -t display in column nicely.

bash - how do I use 2 numbers on a line to create a sequence

I have this file content:
2450TO3450
3800
4500TO4560
And I would like to obtain something of this sort:
2450
2454
2458
...
3450
3800
4500
4504
4508
..
4560
Basically I would need a one liner in sed/awk that would read the values on both sides of the TO separator and inject those in a seq command or do the loop on its own and dump it in the same file as a value per line with an arbitrary increment, let's say 4 in the example above.
I know I can use several one temp file, go the read command and sorts, but I would like to do it in a one liner starting with cat filename | etc. as it is already part of a bigger script.
Correctness of the input is guaranteed so always left side of TOis smaller than bigger side of it.
Thanks
Like this:
awk -F'TO' -v inc=4 'NF==1{print $1;next}{for(i=$1;i<=$2;i+=inc)print i}' file
or, if you like starting with cat:
cat file | awk -F'TO' -v inc=4 'NF==1{print $1;next}{for(i=$1;i<=$2;i+=inc)print i}'
Something like this might work:
awk -F TO '{system("seq " $1 " 4 " ($2 ? $2 : $1))}'
This would tell awk to system (execute) the command seq 10 4 10 for lines just containing 10 (which outputs 10), and something like seq 10 4 40 for lines like 10TO40. The output seems to match your example.
Given:
txt="2450TO3450
3800
4500TO4560"
You can do:
echo "$txt" | awk -F TO '{$2<$1 ? t=$1 : t=$2; for(i=$1; i<=t; i++) print i}'
If you want an increment greater than 1:
echo "$txt" | awk -F TO -v p=4 '{$2<$1 ? t=$1 : t=$2; for(i=$1; i<=t; i+=p) print i}'
Give a try to this:
sed 's/TO/ /' file.txt | while read first second; do if [ ! -z "$second" ] ; then seq $first 4 $second; else printf "%s\n" $first; fi; done
sed is used to replace TO with space char.
read is used to read the line, if there are 2 numbers, seq is used to generate the sequence. Otherwise, the uniq number is printed.
This might work for you (GNU sed):
sed -r 's/(.*)TO(.*)/seq \1 4 \2/e' file
This evaluates the RHS of the substitution command if the LHS contains TO.

Print line having two instances of same string in same line

I am trying to print lines which are having N(2 in this case) number of patterns in it.
For example: (input file)
cat data.txt
hello all
this is a text file
and this line is having one pattern
and this line is having two pattern, and here is another one : pattern. so its two in this line.
in this line pattern is three times , here is two more pattern and pattern
output: (print line containing two strings = pattern)
and this line is having two pattern, and here is another one : pattern. so its two in this line.
I was trying in following direction ,but grep -c is not helping me here.
string=pattern
while read line
do
count=$(echo $line |grep -c $string)
#this always gives me 1, as its a count based on line.
if [ "$count" -eq 2 ];then
echo $line
fi
done <data.txt
any suggestions ?
Using awk
awk 'gsub(/pattern/,"&")==2' file
if you want to pass in the params
awk -vPattern="pattern" -vNum=2 'gsub(Pattern,"&")==Num' file
In your existing code, replace the count= assignment with the following, using gawk:
count=$(echo $line |gawk -F "$string" -- '{print NF-1}')
$string can hold a word or a regular expression. The -F "$string" assignment makes gawk split fields at instances of $string. Therefore, the number of fields NF will be the number of occurrences of $string, plus 1 for whatever comes after the last occurrence of $string (even if that's an empty string). NF-1 is therefore the number of occurrences of $string.
Example: because of -F pattern, gawk will break the line
a pattern b pattern c
into three fields: a, b, and c. Because there are three fields, there are two separators between those fields. Therefore, NF-1, one fewer than the number of fields, is the number of separators between those fields.
Try with:
p1=pattern
n=2
pn="$p1"
for i in $(seq 2 $n); do
pn="$pn.*$p1"
done
pn1="$pn.*$p1"
cat data.txt | egrep "$pn" | egrep -v "$pn1"

Get last four characters from a string

I am trying to parse the last 4 characters of Mac serial numbers from terminal. I can grab the serial with this command:
serial=$(ioreg -l |grep "IOPlatformSerialNumber"|cut -d ""="" -f 2|sed -e s/[^[:alnum:]]//g)
but I need to output just the last 4 characters.
Found it in a linux forum echo ${serial:(-4)}
Using a shell parameter expansion to extract the last 4 characters after the fact works, but you could do it all in one step:
ioreg -k IOPlatformSerialNumber | sed -En 's/^.*"IOPlatformSerialNumber".*(.{4})"$/\1/p'
ioreg -k IOPlatformSerialNumber returns much fewer lines than ioreg -l, so it speeds up the operation considerably (about 80% faster on my machine).
The sed command matches the entire line of interest, and replaces it with the last 4 characters before the " that ends the line; i.e., it returns the last 4 chars. of the value.
Note: The ioreg output line of interest looks something like this:
| "IOPlatformSerialNumber" = "A02UV13KDNMJ"
As for your original command: cut -d ""="" is the same as cut -d = - the shell simply removes the empty strings around the = before cut sees the value. Note that cut only accepts a single delimiter char.
You can also do: grep -Eo '.{4}$' <<< "$serial"
I don't know how the output of ioreg -l looks like, but it looks to me that you are using so many pipes to do something that awk alone could handle:
use = as field separator
vvv
awk -F= '/IOPlatformSerialNumber/ { #match lines containing IOPlatform...
gsub(/[^[:alnum:]]/, "", $2) # replace all non alpha chars from 2nd field
print substr($2, length($2)-3, length($2)) # print last 4 characters
}'
Or even sed (a bit ugly one since the repetition of command): catch the first 4 alphanumeric characters occuring after the first =:
sed -rn '/IOPlatformSerialNumber/{
s/^[^=]*=[^a-zA-Z0-9]*([a-zA-Z0-9])[^a-zA-Z0-9]*([a-zA-Z0-9])[^a-zA-Z0-9]*([a-zA-Z0-9])[^a-zA-Z0-9]*([a-zA-Z0-9]).*$/\1\2\3\4/;p
}'
Test
$ cat a
aaa
bbIOPlatformSerialNumber=A_+23B/44C//55=ttt
IOPlatformSerialNumber=A_+23B/44C55=ttt
asdfasd
The last 4 alphanumeric characters between the 1st and 2nd = are 4C55:
$ awk -F= '/IOPlatformSerialNumber/ {gsub(/[^[:alnum:]]/, "", $2); print substr($2, length($2)-3, length($2))}' a
4C55
4C55
Without you posting some sample output of ioreg -l this is untested and a guess but it looks like all you need is something like:
ioreg -l | sed -r -n 's/IOPlatformSerialNumber=[[:alnum:]]+([[:alnum:]]{4})/\1/'

Resources