Append a line "while read line"

Hello everyone,

I want to process a data file using bash, I would like add a new line RIGHT after the time when i know "another variable met a value"

Like this:

while read line
do
 ...
 if [ $xyz == "ON" ]; then
 #Append a new line "===" to input.txt file after $line position
 #break
 fi
done < input.txt

That mean we don't know the position in advance, so I could not use "sed".
Any help please!
Thank you!

$ cat -n input.txt
     1  test
     2  ON
     3  go
     4  hello
     5  hi
     6  IN
     7  ON
-------------------------------------------------
$ cat i.sh
#!/bin/bash
while read line
do
        line_num=$((line_num+1))
        if [ $line = "ON" ]; then
                echo ${line_num}
        fi
done < input.txt
-------------------------------------------------
$ bash i.sh
2
7

Hi itkamaraj,

Where is the line_num coming from?

And you may mis-understand my initial post: the condition is based on another variable $xyz - not the data from file or $line

can you provide the example input and output file and $xyz variable value...etc...etc

input.txt

To make it easier, let assume xyz=2
So, I want look for the PIN line, then move it next xyz line (meaning: move it to next 2 lines)
The output should be like this:

line_before_pin_1
line_before_pin_2
..
line_after_pin_1
line_after_pin_2
PIN
line_after_pin_3
..
line_after_pin_end

Note that there are 2 actions:

  • remove old PIN line
  • add new PIN line to the right position

Hello fongthai,

Could you please try following and let me know if this helps you. Let's say your Input_file is as follows.

cat  Input_file
line_before_pin_1
line_before_pin_2
PIN
line_after_pin_1
line_after_pin_2
line_after_pin_3
line_after_pin_end

Then following is the code for same.

awk -vxyz=2 '{if($0 ~ /PIN/){while(++a<=xyz){getline;print $0;};print "PIN";a="";next}} 1'   Input_file

Output will be as follows.

line_before_pin_1
line_before_pin_2
line_after_pin_1
line_after_pin_2
PIN
line_after_pin_3
line_after_pin_end

Similarly you could change the value of variable in above code -vxyz=2 to as per your requirement too.

Thanks,
R. Singh

1 Like

Thanks,

That works well, but not easy to understand, can you clear my mind :slight_smile: please
Thanks.

Hello fongthai,

Could you please go through following and let us know if this helps you. Also please do not run following, as following one is only for understanding purposes, I have split it.

awk -vxyz=2       ####creating a variable in awk by -v option whose syntax is -vvariable_name=variable_value or -v variable_name=variable_value, so as per your requirement I have created variable named xyz and set it's value to 2.
'{if($0 ~ /PIN/)  ####Putting an if condition here, to check either a line is having value PIN in it, here $0 represents record(line).
{                 ####if above condition is TRUE, means a line has string PIN in it, so statements from this block will be executed now.
while(++a<=xyz)   ####starting a while loop here, where it should run until the variable named a's value is less than or equal to variable named xyz's value. Here point to be noted is I am giving ++a, which means before checking condition with variable named xyz it will increase the value of variable named a.
{                 ####So if condition in above while loop is TRUE then statements inside while loop will  be executed as follows.
getline;          ####mentioning "getline" here, it's an awk in-built keyword which tells cursor to go to next line(as we all know awk reads line one by one).
print $0;         ####Now when cursor goes to next line, I am giving the print command here, telling awk to print current line, because we need NOT to print the line which have "PIN" in it and print it after the number of lines which are given in variable xyz.
}                 ####while loop is getting closed here now.
;print "PIN";     ####printing the value of string "PIN" as in above while loop awk will print all lines which should be printed before new position of string "PIN", so printing it now.
a="";             ####Nullifying variable a now, reason because whenever next time a match found into your Input_file for string "PIN" into a line, a should be start from 0 else it may carry it's previous values and output could be diferent then.
next              ####stating next here, next is an awk's in-built keyword which forces cursor to move forward and NOT to execute the further all statements.
}}                ####if condition mentioned in line2 above is getting closed here.
1                 ####Mentioning 1 here, which means making a condition TRUE here. So awk works on condition{action/operation} syntax. So if any condition is TRUE it executes the further mentioned actions, so here when we are making condition RUE by mentioning it 1 here and no actions are mentioned by me so awk will do default action which is to print the current line.
                  ####So basically this 1 will print those lines which doesn't have string "PIN" in them.
'   Input_file    ####Mentioning Input_file here.
 

Thanks,
R. Singh

1 Like

Let us be perfectly clear here. The requirements stated in post #1 in this thread and in post #5 in this thread are completely different. Adding a line containing === when $xyz expands to the string ON is in no way related to moving a line that contains only the string PIN 2 lines later in a file when $xyz expands to the string 2 .

To move the first line in a file containing only the string PIN forward $xyz lines (if $xyz expands to a positive decimal value (with or without a leading + ) or backwards $xyz lines (if $xyz expands to a negative decimal value), you could also try:

#!/bin/ksh
xyz=${1:-2}	# set default value for $xyz to 2, but use $1 if present.
if [ "${xyz#[+-]}" != "$xyz" ]
then	# $xyz is a signed value, clear $op
	op=
	# If $xyz is negative, decrement it by one to get the desired offset.
	[ "$xyz" -lt 0 ] && xyz=$((xyz - 1))
else	# $xyz is an unsigned value, set $op to a plus sign.
	op="+"
fi

# Use ed to move the first line that exactly matches the string "PIN" to the
# desired spot.
ed -s input.txt <<EOF
	H
	/^PIN$/;.m$op$xyz
	w
	q
EOF

This was written and tested using a Korn shell, but will work with any shell that uses Bourne shell syntax and performs the parameter and arithmetic expansions required by the POSIX standards.

If this script is invoked with no operands, with the operand 2 , or the operand +2 it will move the 1st line in the file named input.txt that is exactly the string PIN two lines closer to the end of the file. If invoked with the operand -1 , it will move that line one line closer to the start of the file. This will work with any non-zero decimal value as long as the that value is a valid for that line in the given file.

Note that the behavior of this script is slightly different from the script provided by RavinderSingh13:

  1. My script modifies the file input.txt in place. Ravinder's script copies the desired changes to standard output, but does not modify the file Input_file .
  2. My script only moves the 1st line that contains only the string PIN . Ravinder's script moves any line containing the string PIN . Although, if there are two or more lines containing PIN within $xyz lines of each other, only the 1st of those will be moved.
  3. If the specified offset is not valid in the input file, my script prints a diagnostic message and does not change the input file; Ravinder's script moves a line containing PIN to the end of the file.
  4. If there is no line containing PIN , Ravinder's script copies the input file to standard output unchanged; my script prints a diagnostic message and leaves the input file unmodified.
  5. Ravinder's script will move matched lines towards the end of the file; my script will move the 1st matched line forwards or backwards.

Both of our scripts meet your requirements as specified in post #5 in this thread. But, you left a lot of behavior unspecified that our scripts handle in different ways (as noted above).

1 Like

Thanks Don,
You're more than expected pointing out my requirement changed, showing your script and explain the different comparing to Ravinder's one. Perfect!

The reason I changed it just to make it easier for people to help me.

I also want to send my thanks to Ravinder as your clear explanation.

You are great! It made my day :slight_smile:

Try also

awk '$0 ~ SRCH {LN = NR + ++XYZ; TMP = $0; next} NR == LN {print TMP} 1' SRCH=PIN XYZ=2 file
line_before_pin_1
line_before_pin_2
line_after_pin_1
line_after_pin_2
PIN
line_after_pin_3
line_after_pin_end
while read xyz
do
echo "$xyz" >>outputfile
if [ "a$xyz" = "aON" ]
then 
    echo "===" >>outputfile
fi
done <inputfile

Two things to note.
This process creates a new file without modifying the original. You can run this process as many times as you like. Don't forget to delete the outputfile if you re-run.
Secondly, in the 'if' statement the 'a' is added to the beginning of the strings to prevent the script dying if there is a blank line in the input file.