Using Awk or Sed to tack on a statement at the end of a specific line

后端 未结 3 501
花落未央
花落未央 2020-12-02 01:01

I have a file I named poscar1.cif, and I would like to insert the contents of a variable at a specific line in this file.

For example, line 24

相关标签:
3条回答
  • 2020-12-02 01:52

    You can use sed to do it, depending on your answer to dawg's question as

    sed -i -e '24s/$/5.3827/' poscar1.cif
    

    or if it's the pattern

    sed -i -e '/_cell_length_a/s/$/5.3827/' poscar1.cif
    

    The first goes to the line with the given number, the later will apply on any line that matches the pattern in the first set of slashes. In either case it will then "replace" the end of the line with the value between the final two slashes.

    0 讨论(0)
  • 2020-12-02 01:57

    Since the veteran ed utility doesn't get enough attention anymore:

    a=5.3827
    
    ed -s poscar1.cif <<EOF 
    g/^_cell_length_a\$/ s//& $a/
    w
    EOF
    

    ed truly edits a file in place, unlike sed with its -i option[1].

    sed borrowed many features from ed, so there is significant overlap in functionality, but there are also important differences, some of which surface here.

    • -s suppresses ed's status messages.
    • poscar1.cif is the input file to edit in place.
    • <<EOF ... is the here-document that contains the commands for ed - ed requires its commands to come from stdin and each command to be on its own line.
    • g/^_cell_length_a\$/ ... is a (basic) regex (regular expression) that matches all lines that exactly contain _cell_length_a - the g ensures that no error is reported if there's no match at all.
      • Note that the $ is \-escaped to protect it from interpretation by the shell inside the here-document (not strictly necessary in this instance, but good practice).
    • s//& $a/ ... // repeats the search for the most recently used regex on a matching line and replaces the match with itself (&), followed by a space and the value of variable $a.
      • Note that since the opening delimiter (EOF) of the here-document is unquoted, shell variable expansions DO take place; in essence, the contents are treated by the shell like the contents of a double-quoted string.
    • w writes the modified buffer back to the input file.
      • For debugging, use ,p in place of w so as to only print the modified buffer, without writing it back to the file.

    [1] Re in-place updating:

    More precisely, ed preserves the file's existing inode, which ensures that all the file's attributes are preserved.
    However, it does not overwrite individual bytes of the existing file, but reads the entire file into a buffer in memory, and writes the entire buffer to the file when asked to.
    This makes ed suitable only for files small enough to be read into memory as a whole.

    By contrast, sed -i (GNU and BSD sed), its GNU 4.1+ counterpart, awk -i inplace, and also perl -i replace the original file with a newly created one, which implies that they:

    • destroy symlinks(!) - if the input file was a symlink, it is replaced with a regular file of the same name
      • A common scenario where that matters: say your shell initialization file ~/.bashrc is a symlink to a file elsewhere you keep under source control; you then install a tool that uses sed -i to modify ~/.bashrc, which results in it being replaced with a regular file, and the link to your source-controlled version is broken.
      • What's more, BSD sed's behavior even introduces a security risk (see below).
    • do not preserve the original file-creation date (where supported; e.g., on OSX)
    • they do, however,

      • preserve extended attributes (where supported; e.g., on OSX)
      • preserve file permissions

        • Caution: BSD sed introduces a security risk with respect to symlinks (behavior still present as of the version that comes with FreeBSD 10):
          • The symlink's permissions are copied to the replacement file, not the symlink target's. Since symlinks get executable permissions by default, you'll invariably end up with an executable file, whether the input file was executable or not.
        • Fortunately, GNU sed handles this scenario properly.

    sed, gawk, and perl could address the issues above by taking extra steps, but there's one thing that can only be ensured if the original inode is retained, as ed does:

    When a file is being monitored for changes by its inode number (e.g., with tail -f), not preserving the inode breaks that monitoring.

    0 讨论(0)
  • 2020-12-02 02:01

    Using your example, you could do something like this:

    sed -i 's/\(_cell_length_a\)/\1 5.3827/' poscar1.cif
    

    where,

    • the -i option says to edit the file in place, rather than creating a copy
    • the funky looking quoted part is a string specifying a regular expression aka regex
    • poscar1.cif is the file

    The regex syntax is hard to read. The basic format to find and replace is:

    s/find/replace/
    

    Where find is the text of the line you're looking for and replace is the text to replace that text with.

    If we want to use part of the find string in our replacement, we group it by surrounding it with \( and \) and then use \1 to refer to it in the replacement string. The following appends replace to any line consisting of find:

    s/\(find\)/\1replace/
    

    Keep in mind that there are special escape characters or meta characters that you have to treat specially if your string contains them.

    0 讨论(0)
提交回复
热议问题