Keep only nth line if keyword present - bash

Assume a text file that contains lines starting with foo and bar, respectively. Assume further that I would like to print only every fourth line of the ones starting with bar; the lines starting with foo should always be printed.
foo bar qux
# Deliberate empty line
bar baz 1
bar baz 2
bar baz 3
bar baz 4
bar baz 5
bar baz 6
bar baz 7
bar baz 8
# A miscellaneous code comment
The following code prints every fourth line irrespective of the first word and is thus not what I am looking for.
awk '/^bar/ NR == 1 || NR % 4 == 0' infile
What would the correct code be (preferentially with awk)?
EDIT:
Thanks to fedorqui for his excellent suggestion. Considering the potential appearance of empty lines and comments in the input file, I am using the following code:
user$ awk '!/^bar/ || (/^bar/ && !(++c%4))' file
foo bar qux
# Deliberate empty line
bar baz 4
bar baz 8
# A miscellaneous code comment

Just use a counter:
awk '/^foo/ || (/^bar/ && !(++c%4))' file
This prints lines that accomplish either of these:
start with "foo"
start with "bar" and this happens for the 4th time, 8th... That is, every four times a line starts with "bar".
See it in action:
$ cat a
foo1
bar1
bar2
bar3
foo2
foo3
bar4
bar5
bar6
bar7
bar8
bar9
$ awk '/^foo/ || (/^bar/ && !(++c%4))' a
foo1
foo2
foo3
bar4
bar8

This should do the trick:
awk '/^bar/ && NR % 4 == 0 || /^foo/' infile

Related

How to upsert an array with yq

Consider the following hello.yaml:
foos:
- foo: foo1
bar: hello
- foo: foo2
bar: world
If I want to update the bar value where foo = "foo1", I can invoke the following command:
yq '( .foos[] | select(.foo == "foo1") | .bar) |= "goodbye cruel"' hello.yaml
And that correctly outputs:
foos:
- foo: foo1
bar: goodbye cruel
- foo: foo2
bar: world
However, if I do not know that I have an item that matches, I would like to insert the appropriate entries e.g. something like yq '( .foos[] | select(.foo == "foo3") | .bar) ... would output
foos:
- foo: foo1
bar: hello
- foo: foo2
bar: world
- foo: foo3
bar: goodbye cruel
Is there a way in yq to "upsert" the array, or do I have to evaluate if the key exists upfront and perform one of two commands to insert or update?
Many thanks
Like Inian said; there is no upsert operation (at the moment). This is how I would do it - not sure if there is a better way?
yq '
with(.foos ;
select( all_c(.foo != "foo3")) | . += {"foo": "foo3"}
) |
(.foos[] | select(.foo == "foo3") | .bar) = "cool"
' hello.yaml
Explanation:
In the with block, match arrays that don't have foo: foo3, and add it.
Next, find all the elements with foo: foo3 and update them.
Disclaimer: I wrote yq

Multiple "sed" actions on previous results

Have this input:
bar foo
foo ABC/DEF
BAR ABC
ABC foo DEF
foo bar
on the above I need do 4 (sequential) actions:
select only lines containing "foo" (lowercase)
on the selected lines, remove everything but UPPERCASE letters
delete empty lines (if some is created by the previous action)
and on the remaining from the above - enclose every char with [x]
I'm able to solve the above, but need two sed invocations piped together. Script:
#!/bin/bash
data() {
cat <<EOF
bar foo
foo ABC/DEF
BAR ABC
ABC foo DEF
foo bar
EOF
}
echo "Result OK"
data | sed -n '/foo/s/[^A-Z]//gp' | sed '/^\s*$/d;s/./[&]/g'
# in the above it is solved using 2 sed invocations
# trying to solve it using only one invocation,
# but the following doesn't do what i need.. :( :(
echo "Variant 2 - trying to use only ONE invocation of sed"
data | sed -n '/foo/s/[^A-Z]//g;/^\s*$/d;s/./[&]/gp'
output from the above:
Result OK
[A][B][C][D][E][F]
[A][B][C][D][E][F]
Variant 2 - trying to use only ONE invocation of sed
[A][B][C][D][E][F]
[B][A][R][ ][A][B][C]
[A][B][C][D][E][F]
The variant 2 should be also only
[A][B][C][D][E][F]
[A][B][C][D][E][F]
It is possible to solve the above using only by one sed invocation?
sed -n '/foo/{s/[^A-Z]//g;/^$/d;s/./[&]/g;p;}' inputfile
Output:
[A][B][C][D][E][F]
[A][B][C][D][E][F]
Alternative sed approach:
sed '/foo/!d;s/[^A-Z]//g;/./!d;s/./[&]/g' file
The output:
[A][B][C][D][E][F]
[A][B][C][D][E][F]
/foo/!d - deletes all lines that don't contain foo
/./!d - deletes all empty lines

Display Unique Shell Columns

Given we have two formatted strings that are unrelated to each other.
#test.rb
string_1 = "Title\nfoo bar\nbaz\nfoo bar baz boo"
string_2 = "Unrelated Title\ndog cat farm\nspace moon"
How can I use ruby or call shell commands to have each of these string display as columns in terminal? The key is that the data of each string are not building a correlated row, ie this is not a table, rather 2 lists side by side.
Title Unrelated Title
foo bar dog cat farm
baz space moon
foo bar baz boo
You can try using paste and column command together. Note that this is a shell command so spaces between the assignment operator should be corrected.
$ string_1="Title\nfoo bar\nbaz\nfoo bar baz boo"
$ string_2="Unrelated Title\ndog cat farm\nspace moon"
$ paste -d '|' <(echo -e "$string_1") <(echo -e "$string_2") | column -s'|' -t
Title Unrelated Title
foo bar dog cat farm
baz space moon
foo bar baz boo
We paste the lines with | as delimiter and tell column command to use | as a reference to form columns.
In Ruby, you could do it this way:
#!/usr/bin/env ruby
string_1 = "Title\nfoo bar\nbaz\nfoo bar baz boo"
string_2 = "Unrelated Title\ndog cat farm\nspace moon"
a1 = string_1.split("\n")
a2 = string_2.split("\n")
a1.zip(a2).each { |pair| puts "%-20s%s" % [pair.first, pair.last] }
# or
# a1.zip(a2).each { |left, right| puts "%-20s%s" % [left, right] }
This produces:
Title Unrelated Title
foo bar dog cat farm
baz space moon
foo bar baz boo
Hi , If you Use temp files
string_1 = "Title\nfoo bar\nbaz\nfoo bar baz boo"
string_2 = "Unrelated Title\ndog cat farm\nspace moon"
echo -e $string_1 >a.txt
echo -e $string_2 >b.txt
paste a.txt b.txt
I hope it will help.

Bash script for creating text in a file using arguments

Imagine that you have to create many lines in file all with the same text except for one variable:
foo text $variable bar text
foo text $variable bar text
foo text $variable bar text
...
I was wondering if this could be made using a bash script passing it the variable as an argument:
./my_script '1'
./my_script '2'
./my_script '3'
Which would generate this:
foo text 1 bar text
foo text 2 bar text
foo text 3 bar text
Any suggestions or examples on how to do this?
See also http://tldp.org/LDP/abs/html/internalvariables.html#POSPARAMREF:
#!/bin/bash
echo "foo text ${1} bar text"
It's too trivial a task to write a script for.
Here are a couple of possible solutions:
for (( variable=1; variable<10; ++variable )); do
echo foo text $variable bar text
done
or...
for name in Dave Susan Peter Paul; do
echo "Did you know that $name is coming to my party?"
done
This might work for you:
printf "foo text %d bar text\n" {1..10}
foo text 1 bar text
foo text 2 bar text
foo text 3 bar text
foo text 4 bar text
foo text 5 bar text
foo text 6 bar text
foo text 7 bar text
foo text 8 bar text
foo text 9 bar text
foo text 10 bar text
or this:
printf "foo text %s bar text\n" foo bar baz
foo text foo bar text
foo text bar bar text
foo text baz bar text
Assuming you're stuck with that crappy template file:
perl -pe 'BEGIN {$count=0} s/\$variable/ ++$count /ge' file
This will depend on how you are getting "foo text" and "bar text". If it is set statically:
var="foo text $1 bar text"
echo "$var"
Or if it is in separate variables you can concatenate them:
foo="foo text"
bar="bar text"
output="$foo $1 $bar"
echo "$output"

How do I write one-liner script that inserts the contents of one file to another file?

Say I have file A, in middle of which have a tag string "#INSERT_HERE#". I want to put the whole content of file B to that position of file A. I tried using pipe to concatenate those contents, but I wonder if there is more advanced one-line script to handle it.
$ cat file
one
two
#INSERT_HERE#
three
four
$ cat file_to_insert
foo bar
bar foo
$ awk '/#INSERT_HERE#/{while((getline line<"file_to_insert")>0){ print line };next }1 ' file
one
two
foo bar
bar foo
three
four
cat file | while read line; do if [ "$line" = "#INSERT_HERE#" ]; then cat file_to_insert; else echo $line; fi; done
Use sed's r command:
$ cat foo
one
two
#INSERT_HERE#
three
four
$ cat bar
foo bar
bar foo
$ sed '/#INSERT_HERE#/{ r bar
> d
> }' foo
one
two
foo bar
bar foo
three
four

Resources