File: recursive-sed.sh

package info (click to toggle)
libflame 5.2.0-5.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 162,092 kB
  • sloc: ansic: 750,080; fortran: 404,344; makefile: 8,136; sh: 5,458; python: 937; pascal: 144; perl: 66
file content (488 lines) | stat: -rwxr-xr-x 14,012 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
#!/usr/bin/env bash

#
# recursive-sed.sh
#
# Field G. Van Zee
#

print_usage()
{
	# Echo usage info
	echo " "
	echo " "$script_name
	echo " "
	echo " Field G. Van Zee"
	echo " "
	echo " Recusively descend a directory tree and perform sed commands, either on"
	echo " the filename or the file contents, or both." 
	echo " "
	echo " Usage:"
	echo "   ${script_name} [options]"
	echo " "
	echo " The following options are accepted:"
	echo " "
	echo "   -d "
	echo "                 Dry run. Go through all the motions, but don't actually"
	echo "                 apply any of the sed expressions to file names or contents."
	echo "   -N "
	echo "                 Do not proceed recursively into subdirectories; consider"
	echo "                 only the files within the current directory. Default"
	echo "                 behavior is to act recursively."
	echo "   -h "
	echo "                 Consider hidden files and directories. Default behavior is"
	echo "                 to ignore them."
	echo "   -n "
	echo "                 Use svn mv instead of mv when renaming the file."
	echo "                 Notice that this only applies if the filename changes."
	echo "   -p pattern "
	echo "                 Specifies the filename pattern, as would be given to the"
	echo "                 ls utility, to limit which files are affected. Default is"
	echo "                 the to consider all files present."
	echo "   -r dir"
	echo "                 The root directory for the recursive action to be performed."
	echo "                 Default is to use the current working directory."
	echo "   -v [0|1|2]"
	echo "                 verboseness level"
	echo "                 level 0: silent  (no output)"
	echo "                 level 1: default (one line per directory; supress ls stderr)"
	echo "                 level 2: verbose (one line per directory; show ls stderr)"
	echo " "
	echo " At least one of the following option-argument pairs is required:"
	echo " "
	echo "   -f sed_expr "
	echo "                 Specifies the sed expression that will be applied to the"
	echo "                 filenames of the files touched by the script. This expression"
    echo "                 must be a search-and-replace pattern."
	echo "   -c sed_expr "
	echo "                 Specifies the sed expression that will be applied to the"
	echo "                 contents of the files touched by the script. This expression"
    echo "                 should be a search-and-replace pattern."
	echo "   -s sed_script"
	echo "                 Specifies an arbitrary sed script that will be applied to the"
	echo "                 file contents of the files touched by the script."
	echo " "
	echo " Note: -c and -s options are mutually exclusive."
	echo " "
	
	# Exit with non-zero exit status
	exit 1
}




perform_sed()
{
	# Variables set by getopts.
	local exist_dir="$1"
	
	#echo "exist_dir: $exist_dir"

	# The suffix used to create temporary files
	local temp_file_suffix="sed_temp"

	# Check that exist_dir actually exists and is a directory
	if [ ! -d "${exist_dir}" ]; then
		echo "${script_name}: ${exist_dir} does not seem to be a valid directory."
		exit 1
	fi
	
	# Check that the filename sed expression, if given, begins with an 's'.
	if [ -n "$filename_sed_expr" ]; then
		
		# If it's a valid search-and-replace expression, this should return an 's'.
		filename_sed_char=${filename_sed_expr%%/*}
		
		if [ "$filename_sed_char" != "s" ]; then
			echo "${script_name}: sed expression given with -f must be search-and-replace."
			exit 1
		fi
	fi
	
	# Check that the sed script, if given, exists.
	if [ -n "$contents_sed_script" ]; then
		
		if [ ! -f ${contents_sed_script} ]; then
			echo "${script_name}: ${contents_sed_script} is not a regular file or does not exist."
			exit 1
		fi
	fi
	
	# Assume that the sed expression is a search-and-replace. Extract the patterns
	# to match on. (Arbitrary sed expressions should be applied through a sed script.)
	if [ "$filename_sed_expr" != "" ]; then
		filename_sed_match=${filename_sed_expr#s/}
		filename_sed_match=${filename_sed_match%%/*}
	fi
	

	# Get the list of source files in the directory given. Supress stderr if
	# level 0 or 1 verbosity was requested.
	#if [ "$verbose_level" != "2" ]; then
	#	old_filepaths=$(ls -d -b ${exist_dir}/${filename_pattern} 2> /dev/null)
	#else
	#	old_filepaths="$(ls -d -b ${exist_dir}/${filename_pattern})"
	#fi
	
	#echo $old_filepaths
	#echo "$exist_dir/$filename_pattern"
	
	#for old_filepath in $old_filepaths; do
	#echo "exist_dir:    $exist_dir"

	# Find all files that match the pattern in the current directory.
	find "${exist_dir}" -maxdepth 1 -name "${filename_pattern}" -print | while read old_filepath
	do
		#echo "old_filepath: $old_filepath"

		# Skip the current directory.
		if [ "${old_filepath}" == "${exist_dir}" ]; then
			continue
		fi
		
		# Skip any non-regular files.
		if [ ! -f "$old_filepath" ]; then
			
			# And say we are doing so if verboseness was requested.
			if [ "$verbose_level" = "2" ]; then
				echo "${script_name}: Ignoring $old_filepath"
			fi
			continue
		fi
		
		# Strip exist_dir from filename.
		old_filename=${old_filepath##*/}

		# Strip the filename from old_filepath to leave the directory path.
		old_dirpath=${old_filepath%/*}
		
		# Create a new filename from the old one. If a filename sed expression was given,
		# it will be applied now.
		if [ "$filename_sed_expr" != "" ]; then
			new_filename=$(echo "${old_filename}" | sed "${filename_sed_expr}")
		else
			new_filename="${old_filename}"
		fi

		#echo "new_filename: $new_filename"
			
		# Create the filepath to the new file location.
		new_filepath="${old_dirpath}/${new_filename}"
		#echo "new_filepath: $new_filepath"
			
		# Grep for the filename pattern within the filename of the current file.
		if [ "$filename_sed_expr" != "" ]; then
			grep_filename=$(echo "${old_filename}" | grep "${filename_sed_match}")
		fi
		

		# If we are not performing a dry run, proceed.
		if [ -z "$dry_run_flag" ]; then
			
			# Save the old file permissions so we can re-apply them to the
			# new file if its contents change (ie: if it's not just a 'mv',
			# which inherently preserves file permissions).
			old_perms=$(stat -c %a "${old_filepath}")

			# If the old and new filepaths are different, then we start off by
			# renaming the file. (Otherwise, if the old and new filepaths are
			# identical, then we don't need to do anything to the file.) If
			# the user requested that we use svn mv, then do that, otherwise we
			# use regular mv.
			if [ "${old_filepath}" != "${new_filepath}" ]; then
	
				if [ -n "$use_svn_mv_flag" ]; then
	
					svn mv "${old_filepath}" "${new_filepath}"
				else
	
					mv -f "${old_filepath}" "${new_filepath}"
				fi
			fi
		#else

			# A dry run still needs the act upon the "new" file, so if the
			# filepaths are different, simply set the new filepath to the
			# old one. (We won't need the previous value of new_filepath 
			# anymore.)
			#if [ "${old_filepath}" != "${new_filepath}" ]; then
			#	new_filepath="${old_filepath}"
			#fi
		fi

		# Handle the cases that might change the contents of the file.
		if [ "$contents_sed_expr" != "" ] ||
		   [ "$contents_sed_script" != "" ]; then
			
			# Execute the sed command based on whether the sed action was given
			# as a command line expression or a script residing in a file.
			if   [ "$contents_sed_script" != "" ]; then
				
				# Perform the action, saving the result to a temporary file.
				cat "${new_filepath}" | sed -f ${contents_sed_script} \
				                      > ${new_filepath}.${temp_file_suffix}
			
			elif [ "$contents_sed_expr" != "" ]; then
				
				# Perform the action, saving the result to a temporary file.
				cat "${new_filepath}" | sed -e "${contents_sed_expr}" \
				                      > ${new_filepath}.${temp_file_suffix}
			fi
			
			# Check the difference.
			file_diff=$(diff "${new_filepath}" "${new_filepath}.${temp_file_suffix}")
			
			
			# If we are not performing a dry run, proceed.
			if [ -z "$dry_run_flag" ]; then
			
				# If the file contents change.
				if [ -n "$file_diff" ]; then
				
					# Apply the old file permissions to the new file (before we
					# potentially overwrite the old file with the new one).
					chmod ${old_perms} "${new_filepath}.${temp_file_suffix}"

					# Apply the file contents changes to the new filepath (which may
					# or may not be the same as the old filepath).
					mv -f "${new_filepath}.${temp_file_suffix}" "${new_filepath}"

				else
					# Otherwise remove the new temporary file since it is identical
					# to the original.
					rm -f "${new_filepath}.${temp_file_suffix}"
				fi
			else
				# Simply remove the file since we are only performing a dry run.
				rm -f "${new_filepath}.${temp_file_suffix}"
			fi

		fi
		
		# Check for dos2unix. If it's not here, we'll just substitute cat.
		#type_dos2unix=$(type -path dos2unix)
		#if [ -n "$type_dos2unix" ]; then
		#	dos2unix -q ${new_filepath}
		#fi

		# Create a string that indicates what we are changing. We'll use this in
		# the verbose progress echo to indicate how the file is or would be changed.
		if   [ -n "$grep_filename" ] && [ -n "$file_diff" ]; then
			which_matches="filename/contents"
			file_touched="yes"
		elif [ -n "$grep_filename" ] && [ -z "$file_diff" ]; then
			which_matches="filename         "
			file_touched="yes"
		elif [ -z "$grep_filename" ] && [ -n "$file_diff" ]; then
			which_matches="         contents"
			file_touched="yes"
		else
			which_matches=""
			file_touched="no"
		fi
		
		# Be verbose, if requested, about which file we're looking at.
		if [ "$verbose_level" != "0" ]; then
			
			# But we only need to output a line if the file was touched.
			if [ "$file_touched" != "no" ]; then
				
				# Construct a relative filepath by stripping the initial root
				# directory so that the output does not span as many columns on
				# the terminal.
				rel_old_filepath=${old_filepath#${initial_root_dir}/}

				# Add a "dry run" condition to the output if we're doing a dry-run
				# so that the user knows we didn't really change anything.
				if [ -z "$dry_run_flag" ]; then
					echo "$script_name: Changing [${which_matches}] of ${rel_old_filepath}"
				else
					echo "$script_name: Changing (dry run) [${which_matches}] of ${rel_old_filepath}"
				fi
			fi
		fi
		
	done
	
	# Exit peacefully.
	return 0
}




recursive_sed()
{
	# Local variable declarations
	local item sub_items curr_dir this_dir
	
	
	# Extract our argument
	curr_dir="$1"
	

	# Call our function to perform the sed operations on the files in the
	# directory given.
	perform_sed "${curr_dir}"
	

	# If we were asked to act recursively, then continue processing
	# curr_dir's contents.
	if [ "$recursive_flag" = "1" ]; then
		
		# Get a listing of items in the directory according to the hidden
		# files/directories flag.
		if [ -n "$hidden_files_dirs_flag" ]; then

			# Get a listing of the directories in curr_dir (including hidden
			# files and directories).
			sub_items=$(ls -a "$curr_dir")

		else

			# Get a listing of the directories in curr_dir.
			sub_items=$(ls "$curr_dir")
		fi

		#echo "sub_items: $sub_items"
	
		# Descend into the contents of curr_dir, calling recursive_sed on
		# any items that are directories.
		find "${curr_dir}" -maxdepth 1 -name "*" -print | while read item
		do

			#echo "conisdering item: $item"

			# Skip the current directory.
			if [ "${item}" == "${curr_dir}" ]; then
				continue
			fi

			# If item is a directory, descend into it.
			if [ -d "$item" ]; then
			
				#echo "item is dir: $item"

				recursive_sed "$item"
			fi
		done

	fi
	
	
	# Return peacefully
	return 0
}




main()
{
	# Variables set by getopts.
	dry_run_flag=""
	hidden_files_dirs_flag=""
	use_svn_mv_flag=""
	filename_pattern=""
	root_dir=""
	initial_root_dir=""
	verbose_level=""
	filename_sed_expr=""
	contents_sed_expr=""
	contents_sed_script=""

	recursive_flag="1"	

	
	# Get the script name
	script_name=${0##*/}
	
	
	# Local variable declarations.
	local item sub_items this_dir
	
	
	# Process our command line options.
	while getopts ":c:df:hp:r:s:nNv:" opt; do
		case $opt in
			d  ) dry_run_flag="1" ;;
			h  ) hidden_files_dirs_flag="1" ;;
			n  ) use_svn_mv_flag="1" ;;
			N  ) recursive_flag="0" ;;
			v  ) verbose_level="$OPTARG" ;;
			p  ) filename_pattern="$OPTARG" ;;
			r  ) root_dir="$OPTARG" ;;
			f  ) filename_sed_expr="$OPTARG" ;;
			c  ) contents_sed_expr="$OPTARG" ;;
			s  ) contents_sed_script="$OPTARG" ;;
			\? ) print_usage
		esac
	done
	shift $(($OPTIND - 1))
	
	
	# Make sure we've parsed all command line arguments by now.
	if [ $# != "0" ]; then
		echo "${script_name}: Unparsed command line arguments! Try running with no arguments for help."
		exit 1
	fi
	
	
	# Make sure we received at least one of the required options.
	if [ -z "$filename_sed_expr" ] &&
	   [ -z "$contents_sed_expr" ] &&
	   [ -z "$contents_sed_script" ]; then
		print_usage
	fi
	
	
	# Make sure that both a file contents sed expression and sed script were
	# not given.
	if [ "$contents_sed_expr"   != "" ] &&
	   [ "$contents_sed_script" != "" ] ; then
		echo "${script_name}: The -c and -s options may not be used at the same time."
		exit 1
	fi


	# Make sure that verboseness level is valid.
	if [ "$verbose_level" != "0" ] && 
	   [ "$verbose_level" != "1" ] &&
	   [ "$verbose_level" != "2" ]; then
		verbose_level="1"
	fi
	
	# Prepare the filename pattern arguments to perform_sed().
	if [ "$filename_pattern" = "" ] ; then
		filename_pattern='*'
	fi

	# Prepare the directory arguments to perform_sed().
	if [ "$root_dir" != "" ] ; then
		
		# Strip / from end of directory paths, if there is one.
		root_dir=${root_dir%/}
	else
		root_dir=$PWD
	fi
	initial_root_dir=${root_dir}
	

	#echo "root_dir: $root_dir"


	# Begin recursing on the root directory.
	recursive_sed "$root_dir"


	# Exit peacefully
	return 0
}




# The script's main entry point, passing all parameters given.
main "$@"