I had a need to perform a search and replace operation on all files in a directory of files. Effectively I needed a recursive sed. I couldn’t find one and wrote this script. This is version .1, but worked for my needs, hopefully others find it useful. It is probably a smart practice to backup your set of files before running any batch operation on them. The script is released under the GPL2 License.
#!/bin/bash # –––––––––––––––––––––– # # Shell program to run a search and replace operation on files recursively. # # Copyright 2008, Steve Francia <steve.francia+search_and_replace@gmail.com>. # # This program is free software; you can redistribute it and/or # modify it under the terms of the <span class="caps">GNU</span> General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but <span class="caps">WITHOUT</span> <span class="caps">ANY</span> <span class="caps">WARRANTY</span>; without even the implied warranty of # <span class="caps">MERCHANTABILITY</span> or <span class="caps">FITNESS</span> <span class="caps">FOR</span> A <span class="caps">PARTICULAR</span> <span class="caps">PURPOSE</span>. See the <span class="caps">GNU</span> # General Public License for more details. # # Description: # # # Usage: # # search_and_replace.sh [ -h | —help ] [<span class="caps">OPTIONS</span>] [<span class="caps">ARGUMENT1</span>] [<span class="caps">ARGUMENT2</span>] # # Options: # # -h, —help Display this help message and exit. # —version Display program version. # # Arguments: # # <span class="caps">ARGUMENT1</span> The token to search for. # <span class="caps">ARGUMENT2</span> The token to replace it with. # # External programs: # # getopt - parse command options (enhanced) # mktemp - make temporary filename (unique) # sed - stream editor # basename - strip directory and suffix from filenames # # Revision History: # # 2008-07-25 Initial Script Created # See ChangeLog for additional changes # # # –––––––––––––––––––––– ##### Preamble ##### # –––––––––––––––––––––– # Constants # –––––––––––––––––––––– declare -r <span class="caps">PROGNAME</span>=$(basename $0) declare -r <span class="caps">VERSION</span>="0.1" declare -r <span class="caps">DESCRIPTION</span>="a Shell program to run a search and replace operation on files recursively." ##### Functions ##### # –––––––––––––––––––––– # Functions # –––––––––––––––––––––– function clean_up() { # –––––––––––––––––––––– # Function to remove temporary files and other housekeeping # No arguments # –––––––––––––––––––––– #rm -f ${TEMP_FILE1} return } # end of clean_up function error_exit() { # –––––––––––––––––––––– # Function for exit due to fatal program error # Arguments: # 1 (optional) string containing descriptive error message # –––––––––––––––––––––– echo "${<span class="caps">PROGNAME</span>}: ${1:-"Unknown Error"}" >&2 clean_up exit 1 } # end of error_exit function graceful_exit() { # –––––––––––––––––––––– # Function called for a graceful exit # No arguments # –––––––––––––––––––––– clean_up exit } # end of graceful_exit function signal_exit() { # –––––––––––––––––––––– # Function to handle termination signals # Arguments: # 1 (optional) signal_spec # –––––––––––––––––––––– case $1 in <span class="caps">INT</span>) echo "$<span class="caps">PROGNAME</span>: Program aborted by user" >&2 clean_up exit ;; <span class="caps">TERM</span>) echo "$<span class="caps">PROGNAME</span>: Program terminated" >&2 clean_up exit ;; *) error_exit "$<span class="caps">PROGNAME</span>: Terminating on unknown signal" ;; esac } # end of signal_exit function make_temp_files() { # –––––––––––––––––––––– # Function to create temporary files # No arguments # –––––––––––––––––––––– # Use user's local tmp directory if it exists if [ -d ~/tmp ]; then TEMP_DIR=~/tmp else TEMP_DIR=/tmp fi # Temp file for this script, using paranoid method of creation to # insure that file name is not predictable. This is for security # to avoid "tmp race" attacks. If more files are needed, create # using the same form. TEMP_FILE1=$(mktemp -q "${TEMP_DIR}/${<span class="caps">PROGNAME</span>}.$$.<span class="caps">XXXXXX</span>") if [ "$TEMP_FILE1" = "" ]; then error_exit "cannot create temp file!" fi } # end of make_temp_files function usage() { # –––––––––––––––––––––– # Function to display usage message (does not exit) # No arguments # –––––––––––––––––––––– echo "Usage: ${<span class="caps">PROGNAME</span>} [-h | —help] [<span class="caps">OPTIONS</span>] [<span class="caps">SEARCH</span>] [<span class="caps">REPLACE</span>]" } # end of usage function helptext() { # –––––––––––––––––––––– # Function to display help message for program # No arguments # –––––––––––––––––––––– cat <<eof ${progname}="" ${version}="" this="" is="" a="" program="" to="" ${description}.="" $(usage)="" options:="" -h,="" —help="" display="" help="" message="" and="" exit.="" -t,="" —test="" what="" actualy="" being="" passed="" sed.="" -e="" [ext],="" —extension="" [ext]="" only="" replace="" text="" found="" in="" files="" with="" extension="" —version="" version.="" arguments:="" search="" the="" token="" for.="" it="" with.="" hints:="" please="" escape="" your="" strings.="" if="" you="" are="" unsure,="" test="" -t="" first.="" eof="" }="" #="" end="" of="" helptext="" function="" search_and_replace()="" {="" ––––––––––––––––––––––="" [insert="" description]="" 1="" (required)="" arg1="" 2="" arg2="" [="" $testmode="" -ne="" 0="" ]="" ;="" then="" echo="" passing="" s="" $1="" $2="" g'="" into="" sed"="" graceful_exit;="" fi="" fatal="" error="" required="" arguments="" missing="" $1"="" ];="" error_exit="" search_and_replace:="" argument="" 1"="" $2"="" 2"="" sed="" $1"'="" $2"'="" $file"=""> $TEMP_FILE1 mv $TEMP_FILE1 "$file" return } # end of search_and_replace # Recursive Directory Traverser # Author: Kaz Kylheku # Date: Feb 27, 1999 # Copyright 1999 function recurse() { # Function parameter usage: # $1 directory to search # $2 pattern to search for # $3 command to execute # $4 secret argument for passing down path if [ "$1" = "" ]; then error_exit "recurse: missing argument 1" fi if [ "$2" = "" ]; then error_exit "recurse: missing argument 2" fi if [ "$3" = "" ]; then error_exit "recurse: missing argument 3" fi local file local path if [ "$4" = "" ] ; then path="${1%/}/" else path="$4$1/" fi if cd "$1" ; then for file in $2; do if [ -f "$file" -o -d "$file" ]; then eval "$3" fi done for file in .* * ; do if [ "$file" = "." -o "$file" = ".." ] ; then continue fi if [ -d "$file" -a ! -L "$file" ]; then recurse "$file" "$2" "$3" "$path" fi done cd .. fi } # –––––––––––––––––––––– # Program starts here # –––––––––––––––––––––– ##### Initialization And Setup ##### ## Set file creation mask so that all files are created with 600 ## permissions. ## #umask 066 # Trap <span class="caps">TERM</span>, <span class="caps">HUP</span>, and <span class="caps">INT</span> signals and properly exit trap "signal_exit <span class="caps">TERM</span>" <span class="caps">TERM</span> <span class="caps">HUP</span> trap "signal_exit <span class="caps">INT</span>" <span class="caps">INT</span> ## Create temporary file(s) # make_temp_files ##### Command Line Processing ##### # if at least one argument is required… if [ $# -eq 0 ]; then echo "This is ${<span class="caps">PROGNAME</span>} ${<span class="caps">VERSION</span>}" usage clean_up exit 1 fi # Note that we use `"$@"' to let each command-line parameter expand to # a separate word. The quotes around `$@' are essential! We need # GETOPT_TEMP as the `eval set —' would nuke the return value of # getopt. GETOPT_TEMP=$(getopt -o +te:h —long test,extension:,help,version -n "$<span class="caps">PROGNAME</span>" — "$@") if [ $? != 0 ] ; then error_exit "Error parsing command line. Terminating…" fi # Note the quotes around `$GETOPT_TEMP': they are essential! eval set — "$GETOPT_TEMP" # no error checking necessary; sanity of command line and required # arguments has been checked by getopt program ext="*"; testmode=0; while true ; do case $1 in -t|—test) echo "<span class="caps">TEST</span> <span class="caps">MODE</span>" ; testmode=1; shift ;; -e|—extension) echo "search for extension: $2" ext=$2 shift 2 ;; -h|—help) helptext ; graceful_exit ;; —version) echo "${<span class="caps">PROGNAME</span>} ${<span class="caps">VERSION</span>}" ; shift ;; —) shift ; break ;; *) # should be impossible to reach: getopt should have caught # an error error_exit "This should not have happened; unknown option '$1'. Terminating…" ;; esac done unset GETOPT_TEMP # processing remaining arguments for the client if [ $# -ne 0 ]; then echo "Searching for: $@" fi ##### Main Logic ##### recurse `/bin/pwd` "*.$ext" "search_and_replace $1 $2" graceful_exit # end of search_and_replace.sh </eof></steve.francia+search_and_replace@gmail.com>














great. I have to try your
great. I have to try your code. Thanks. // Jadu, unstableme.blogspot.com
A little detail
I like to thanks and congratulate you for your so well written bash script.
It worked fine to me!
But when I using it I typed some expression wrong and this caused a error message in sed. But program didn't stop and the original files was replaced by the temporary blank files.
To avoid this replacement in situations like that I put a conditional expression considering the value of "$?" before do the replacement.
So:
...
sed -e 's/'"$1"'/'"$2"'/g' "$file" > $TEMP_FILE1
if [ "$?" = "0" ]; then
mv $TEMP_FILE1 "$file"
fi
...
Thanks one more time!
Post new comment