Search code examples
bashsedifs

Preventing leading spaces from getting ignored using IFS=


I am trying to insert few lines before a specific line in an xml file. Though it's working but the formatting is not retained. Leading whitespaces are getting ignored. I know that we have to use IFS= and i also cross-checked it in the following link besides many others but to no avail. :(

Can anyone please point out the mistake i am committing here?

Preserving leading white space while reading>>writing a file line by line in bash

while read line
do
    pattern=keepDependencies
    input_file=/home/john/data_file
    file_to_change="${backup_dir}/"$line"/config.xml"

    while IFS= read -r insert_text
    do
        sed -i "/$pattern/i $insert_text" $file_to_change
    done < "$input_file"
done < days_to_keep_absent



Data File:

[john ~]$ cat data_file
  <logRotator>
        <daysToKeep>-1</daysToKeep>
        <numToKeep>5</numToKeep>
        <artifactDaysToKeep>-1</artifactDaysToKeep>
        <artifactNumToKeep>-1</artifactNumToKeep>
  </logRotator>



config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<project>
  <actions/>
  <description>I&apos;ll clean all the temporary permissions</description>
  <keepDependencies>false</keepDependencies>
  <properties>
    <hudson.security.AuthorizationMatrixProperty>
    ...
    ...



Output:

<?xml version='1.0' encoding='UTF-8'?>
<project>
  <actions/>
  <description>I&apos;ll clean all the temporary permissions</description>
<logRotator>
<daysToKeep>-1</daysToKeep>
<numToKeep>5</numToKeep>
<artifactDaysToKeep>-1</artifactDaysToKeep>
<artifactNumToKeep>-1</artifactNumToKeep>
</logRotator>
  <keepDependencies>false</keepDependencies>
  <properties>
    <hudson.security.AuthorizationMatrixProperty>
    ...
    ...

Solution

  • It's not read which is giving you problems. It's sed.

    The usual (and, as far as I know, the only Posix-compatible) way of issuing an i command is to follow it immediately with a backslash and a newline. The argument consists of the subsequent lines up to the first one not terminated with a backslash:

    /pattern/i\
        This text is inserted\
    So is this text.
    

    GNU sed allows the inserted text to start on the same line as the i, following any whitespace. And that is why your whitespace is being deleted.

    Try this:

    while read line
    do
      pattern=keepDependencies
      input_file=/home/john/data_file
      # Note: I fixed quoting in the following line.
      file_to_change="$backup_dir/$line/config.xml"
    
      while IFS= read -r insert_text
      do
        # Note: \\ is reduced to \ because it is inside a double-quoted string
        # The newline is inserted directly. So sed sees i\<newline><inserted text>
        sed -i "/$pattern/i\\
    $insert_text" "$file_to_change"
      done < "$input_file"
    done < days_to_keep_absent
    

    I find that style a little hard to read, so I would usually do something like this:

    ICMD='i\
    '
    
    # ...
    
    sed -i "/$pattern/$ICMD$insert_text" "$file_to_change"