如何编辑文本文件中当前的前一行?

时间:2021-11-15 01:03:49

So what I need exactly. I have a file that I looping line by line and when I'll found the word "search" I need to return on previous line and change the word "false" to "true" inside that line, but only on that line not for all file. I'm newbie in bash and that all that I have.

所以我需要的确切。我有一个文件,我逐行循环,当我发现单词“搜索”时,我需要在前一行返回并在该行内更改单词“false”为“true”,但仅在该行上不是为了所有文件。我是bash的新手,而且我拥有的一切。

    file="/u01/MyFile.txt"
    count=0
    while read line
    do
    ((count++))
    if [[ $line == *"[search]"* ]]
    then
        ?????????????
    fi
    done < $file

3 个解决方案

#1


You could do the whole thing in pure bash like this:

你可以用纯粹的bash做这样的事情:

# Declare a function process_file doing the stuff
process_file() {
    # Always have the previous line ready, hold off printing
    # until we know if it needs to be changed.
    read prev

    while read line; do
        if [[ $line == *"[search]"* ]]; then
            # substitute false with true in $prev. Use ${prev//false/true} if
            # several occurrences may need to be replaced.
            echo "${prev/false/true}"
        else
            echo "$prev"
        fi

        # remember current line as previous for next turn
        prev="$line"
    done

    # in the end, print the last line (it was saved as $prev) in the last
    # loop iteration.
    echo "$prev"
}

# call function, feed file to it.
process_file < file

However, there are tools that are better suited to this sort of file processing than pure bash and that are commonly used in shell scripts: awk and sed. These tools process a file by reading line after line1 from it and running a piece of code for each line individually, preserving some state between lines (not unlike the code above) and come with more powerful text processing facilities.

但是,有些工具比纯bash更适合这种文件处理,并且通常用于shell脚本:awk和sed。这些工具通过读取line1之后的行来处理文件,并分别为每行运行一段代码,保留行之间的某些状态(与上面的代码不同),并提供更强大的文本处理工具。

For this, I'd use awk:

为此,我使用awk:

awk 'index($0, "[search]") { sub(/false/, "true", prev) } NR != 1 { print prev } { prev = $0 } END { print prev }' filename

That is:

index($0, "[search]") {       # if the currently processed line contains
  sub(/false/, "true", prev)  # "[search]", replace false with true in the
                              # saved previous line. (use gsub if more than
                              # one occurrence may have to be replaced)
}
NR != 1 {                     # then, unless we're processing the first line
                              # and don't have a previous line,
  print prev                  # print the previous line
}
{                             # then, for all lines:
  prev = $0                   # remember it as previous line for the next turn
}
END {                         # and after the last line was processed,       
  print prev                  # print the last line (that we just saved
                              # as prev)
}

You could also use sed:

你也可以使用sed:

sed '/\[search\]/ { x; s/false/true/; x; }; x; ${ p; x; }; 1d' filename

...but as you can see, sed is somewhat more cryptic. It has its strengths, but this problem doesn't play to them.

......但正如你所看到的,sed有点神秘。它有其优点,但这个问题并没有发挥作用。

Addendum, as requested: The main thing to know is that sed reads line into something called the pattern space (on which most commands operate) and has a hold buffer on the side where you can save things between lines. We'll use the hold buffer to hold the current previous line. The code works as follows:

附录,根据要求:要知道的主要事情是,sed读取行称为模式空间(大多数命令在其上运行)并且在一侧可以保存行之间的保持缓冲区。我们将使用保持缓冲区来保存当前的前一行。代码的工作原理如下:

/\[search\]/ {     # if the currently processed line contains [search]
  x                # eXchange pattern space (PS) and hold buffer (HB)
  s/false/true/    # replace false with true in the pattern space
  x                # swap back. This changed false to true in the PS.
                   # Use s/false/true/g for multiple occurrences.
}
x                  # swap pattern space, hold buffer (the previous line
                   # is now in the PS, the current in the HB)
${                 # if we're processing the last line,
  p                # print the PS
  x                # swap again (current line is now in PS)
}
1d                 # If we're processing the first line, the PS now holds
                   # the empty line that was originally in the HB. Don't
                   # print that.

                   # We're dropping off the end here, and since we didn't
                   # disable auto-print, the PS will be printed now.
                   # That is the previous line except if we're processing
                   # the last line (then it's the last line)

Well, I did warn you that sed is somewhat more cryptic than awk. A caveat of this code is that it expects the input file to have more than one line.

好吧,我确实警告过你,sed比awk更神秘。这段代码的一个警告是它希望输入文件有多行。

1 In awk's case, it's records that don't have to be lines but are lines by default.

1在awk的情况下,它的记录不必是行,而是默认的行。

#2


A very simple approach would be to read 2 lines at a time and then check for the condition in the second line and replace the previous line.

一种非常简单的方法是一次读取2行,然后检查第二行中的条件并替换前一行。

while read prev_line               # reads every 1st line
do
 read curr_line                    # reads every 2nd line

if [[ $curr_line == *"[search]"* ]]; then
           echo "${prev_line/false/true}"   
           echo "$curr_line
        else
            echo "$prev_line"
            echo "$curr_line"
        fi 
done < "file.txt"

#3


The correct version of your way of doing this would be:

你这样做的正确版本是:

file="/u01/MyFile.txt"
count=0

while read line
do
  ((count++))
  if [[ $line == *"[search]"* ]]
  then 
    sed -i.bak "$((count-1))s/true/false/" $file
  fi
done < $file

#1


You could do the whole thing in pure bash like this:

你可以用纯粹的bash做这样的事情:

# Declare a function process_file doing the stuff
process_file() {
    # Always have the previous line ready, hold off printing
    # until we know if it needs to be changed.
    read prev

    while read line; do
        if [[ $line == *"[search]"* ]]; then
            # substitute false with true in $prev. Use ${prev//false/true} if
            # several occurrences may need to be replaced.
            echo "${prev/false/true}"
        else
            echo "$prev"
        fi

        # remember current line as previous for next turn
        prev="$line"
    done

    # in the end, print the last line (it was saved as $prev) in the last
    # loop iteration.
    echo "$prev"
}

# call function, feed file to it.
process_file < file

However, there are tools that are better suited to this sort of file processing than pure bash and that are commonly used in shell scripts: awk and sed. These tools process a file by reading line after line1 from it and running a piece of code for each line individually, preserving some state between lines (not unlike the code above) and come with more powerful text processing facilities.

但是,有些工具比纯bash更适合这种文件处理,并且通常用于shell脚本:awk和sed。这些工具通过读取line1之后的行来处理文件,并分别为每行运行一段代码,保留行之间的某些状态(与上面的代码不同),并提供更强大的文本处理工具。

For this, I'd use awk:

为此,我使用awk:

awk 'index($0, "[search]") { sub(/false/, "true", prev) } NR != 1 { print prev } { prev = $0 } END { print prev }' filename

That is:

index($0, "[search]") {       # if the currently processed line contains
  sub(/false/, "true", prev)  # "[search]", replace false with true in the
                              # saved previous line. (use gsub if more than
                              # one occurrence may have to be replaced)
}
NR != 1 {                     # then, unless we're processing the first line
                              # and don't have a previous line,
  print prev                  # print the previous line
}
{                             # then, for all lines:
  prev = $0                   # remember it as previous line for the next turn
}
END {                         # and after the last line was processed,       
  print prev                  # print the last line (that we just saved
                              # as prev)
}

You could also use sed:

你也可以使用sed:

sed '/\[search\]/ { x; s/false/true/; x; }; x; ${ p; x; }; 1d' filename

...but as you can see, sed is somewhat more cryptic. It has its strengths, but this problem doesn't play to them.

......但正如你所看到的,sed有点神秘。它有其优点,但这个问题并没有发挥作用。

Addendum, as requested: The main thing to know is that sed reads line into something called the pattern space (on which most commands operate) and has a hold buffer on the side where you can save things between lines. We'll use the hold buffer to hold the current previous line. The code works as follows:

附录,根据要求:要知道的主要事情是,sed读取行称为模式空间(大多数命令在其上运行)并且在一侧可以保存行之间的保持缓冲区。我们将使用保持缓冲区来保存当前的前一行。代码的工作原理如下:

/\[search\]/ {     # if the currently processed line contains [search]
  x                # eXchange pattern space (PS) and hold buffer (HB)
  s/false/true/    # replace false with true in the pattern space
  x                # swap back. This changed false to true in the PS.
                   # Use s/false/true/g for multiple occurrences.
}
x                  # swap pattern space, hold buffer (the previous line
                   # is now in the PS, the current in the HB)
${                 # if we're processing the last line,
  p                # print the PS
  x                # swap again (current line is now in PS)
}
1d                 # If we're processing the first line, the PS now holds
                   # the empty line that was originally in the HB. Don't
                   # print that.

                   # We're dropping off the end here, and since we didn't
                   # disable auto-print, the PS will be printed now.
                   # That is the previous line except if we're processing
                   # the last line (then it's the last line)

Well, I did warn you that sed is somewhat more cryptic than awk. A caveat of this code is that it expects the input file to have more than one line.

好吧,我确实警告过你,sed比awk更神秘。这段代码的一个警告是它希望输入文件有多行。

1 In awk's case, it's records that don't have to be lines but are lines by default.

1在awk的情况下,它的记录不必是行,而是默认的行。

#2


A very simple approach would be to read 2 lines at a time and then check for the condition in the second line and replace the previous line.

一种非常简单的方法是一次读取2行,然后检查第二行中的条件并替换前一行。

while read prev_line               # reads every 1st line
do
 read curr_line                    # reads every 2nd line

if [[ $curr_line == *"[search]"* ]]; then
           echo "${prev_line/false/true}"   
           echo "$curr_line
        else
            echo "$prev_line"
            echo "$curr_line"
        fi 
done < "file.txt"

#3


The correct version of your way of doing this would be:

你这样做的正确版本是:

file="/u01/MyFile.txt"
count=0

while read line
do
  ((count++))
  if [[ $line == *"[search]"* ]]
  then 
    sed -i.bak "$((count-1))s/true/false/" $file
  fi
done < $file