Search code examples
regexawksedspecial-characters

Escape special characters when replacing passwords in multiple files with a bash-script


I found lot's of close questions, but not the exact fit to my use case, so had to open a new one.

Guess a system that has a password consisting of all lowercase and uppercase letters, numbers and special characters (a-z,A-Z,0-9, #) in multiple configuration files (the same password is used by a web-application on main domain, its sub-domains and all the relevant databases). Example of such file with a password would be:

$databases = array (
  'default' =>
  array (
    'default' =>
    array (
      'database' => 'database_name',
      'username' => 'datbase_user',
      'password' => 'yv[48k2)KMGx{g1b',
      'host' => 'localhost',
      'port' => '',
      'driver' => 'mysql',
      'collation' => 'utf8mb4_general_ci',
      'charset' => 'utf8mb4',
      'prefix' => '',
    ),
  ),
);

I am trying to write a bash-code that takes in automatically generated new password (with the same algorithm - all lowercase and uppercase letters, numbers and special characters), finds all occurrences of the old password in home directory of respective parent domain and its subdomains and replaces with the new password. The following one-liner works fine:

grep -rlF "$old_pass" /path/to/directory | xargs sed -i "s/$old_pass/$new_pass/g"

for passwords consisting of only letters and digits (e.g. qVYYgB5bRxUYpHg). However, while the grep part works fine, the sed part fails as soon as the system adds special characters to a generated password (e.g.aN#zDU>lve15Uwqn) giving:

sed -i "s/$old_pass/$new_pass/g" password.php
sed: -e expression #1, char 37: unterminated `s' command

What is the best way to replace all occurrences of an existing password that consists of all the letters, numbers and special characters stored in different variables in multiple files?


Solution

  • Better to use perl here due to it's support of /\Q...\E/ regex pattern that treats all letters within these tags literally (no special regex meaning).

    Here is an example:

    cat file
    
    abc123
    pass=yv[48k2)KMGx{g1b
    foobar
    

    Now use this perl command:

    old_pass='yv[48k2)KMGx{g1b'
    new_pass='aN#zDU>lve15Uwqn'
    
    perl -pe "s~\\Q${old_pass}\\E~\\Q$new_pass~g" file
    

    abc123
    pass=aN\#zDU\>lve15Uwqn
    foobar
    

    If you don't have perl, then you may use this awk that uses sting search without using any regex:

    awk -v old="$old_pass" -v new="$new_pass" 'n=index($0, old) { 
       $0 = substr($0, 1, n-1) new substr($0, n + length(old)) } 1' file