Nesting Find and sed command in if else - regex

I am using a find and sed command to replace characters in a file. see the code 1 below
find . -type f -exec sed -i '/Subject/{:a;s/(Subject.*)Subject/\1SecondSubject/;tb;N;ba;:b}' {} +
Given that I have multiple files I need to replace. In a given situation, the Subject I am trying to replace is not available.
Is there a way I can first check if the file contains the attribute 'Subject' if not I need to execute another command. i.e
Check if the file contains character 'Subject'
If true then execute code1 above
If there is no instance of Subject execute code 2 below
find . -name "*.html" -exec rename 's/.html$/.xml/' {} ;
Any Ideas? Thanks in advance

Something like this should work.
find . -type f \( \
-exec grep -q "Subject" {} \; \
-exec sed -i '/Subject/{:a;s/(Subject.*)Subject/\1SecondSubject/;tb;N;ba;:b}' {} \; \
-o \
-exec rename 's/.html$/.xml/' {} \; \)
-exec takes the exit code of the command it executes, so -exec grep -q "Subject" {} \; will only be true if the grep is true. And since the short circuit -o (or) has a lower precedence than the implied -a (and) between the other operators it should conversely only get executed if the grep is false.

You can use find in a process substitution like this:
while IFS= read -d'' -r file; do
echo "processing $file ..."
if grep -q "/Subject/" "$file"; then
sed -i '{:a;s/(Subject.*)Subject/\1SecondSubject/;tb;N;ba;:b}' "$file"
else if [[ $file == *.html ]]; then
rename 's/.html$/.xml/' "$file"
fi
done < <(find . -type f -print0)

Related

Strip unwanted characters from a series of text files

Hi I've got a list of csv files which need to be formatted properly by getting rid of some unwanted characters.
original:
9: ["2019-4-24",-7.101458109105941]
10: ["2019-5-6",-7.050609022950812]
100: ["2019-5-6",-7.050609022950812]
I'd like to modify as:
2019-4-24,-7.101458109105941
2019-5-6,-7.050609022950812
2019-5-6,-7.050609022950812
There are dozens of files in this format and I was thinking of writing a sed command which makes a series of null substitutions for all the files in directory, but these don't seem to work.
find ./ -type f -exec sed -i '' -e "s/^[[:space:]]*//" {} \;
find ./ -type f -exec sed -i '' -e "s/\[//" {} \;
find ./ -type f -exec sed -i '' -e "s/\]//" {} \;
Many thanks for suggestions.
I found this to work on my linux machine.
find ./ -type f -exec sed -i "s/^.\+\[//;s/\"//g;s/\]//" {} \;
Which, from what I gather is equivalent to the following in macOS:
find ./ -type f -exec sed -i '' "s/^.\+\[//;s/\"//g;s/\]//" {} \;
It comprises of 3 substitutions(separated by semicolon):
s/^.\+\[// deletes everything from the start to the "[" character.
s/\"//g deletes all occurences of the double quote character.
s/\]// deletes the final "]" at the end.
And please make a backup or something if you are going to use sed -i.

SED: replace semvers of multiple files

[context] My script needs to replace semvers of multiple .car names with commit sha. In short, I would like that every dev_CA_1.0.0.car became dev_CA_6a8zt5d832.car
ADDING commit sha right before .car was pretty trivial. With this, I end up with dev_CA_1.0.0_6a8zt5d832.car
find . -depth -name "*.car" -exec sh -c 'f="{}"; \
mv -- "$f" $(echo $f | sed -e 's/.car/_${CI_COMMIT_SHORT_SHA}.car/g')' \;
But I find it incredibly difficult to REPLACE. What aspect of sed am I misconceiving trying this:
find . -depth -name "*.car" -exec sh -c 'f="{}"; \
mv -- "$f" $(echo $f | sed -r -E 's/[0-9\.]+.car/${CI_COMMIT_SHORT_SHA}.car/g')
or this
find . -depth -name "*.car" -exec sh -c 'f="{}"; \
mv -- "$f" $(echo $f | sed -r -E 's/^(.*_)[0-9\.]+\.car/\1${CI_COMMIT_SHORT_SHA}\.car/g')' \;
no matches found: f="{}"; mv -- "$f" $(echo $f | sed -r -E ^(.*_)[0-9.]+.car/1684981321531.car/g)
or multiple variants:
\ escaping (e.g. \\.)
( and ) escaping (e.g. \() (I read somewhere that regex grouping with sed requires some care with ())
Is there a more direct way to do it?
Edit
$f getting in sed are path looking like
./somewhere/some_project_CA_1.2.3.car
./somewhere_else/other_project_CE_9.2.3.car
You may use
sed "s/_[0-9.]\{1,\}\.car$/_${CI_COMMIT_SHORT_SHA}.car/g"
See the online demo
Here, sed is used with a POSIX ERE expression, that matches
_ - an underscore
[0-9.]\{1,\} - 1 or more digits or dots
\.car - .car (note that a literal . must be escaped! a . pattern matches any char)
$ - end of string.
Can you try this :
export CI_COMMIT_SHORT_SHA=6a8zt5d832
find . -depth -name "*.car" -exec sh -c \
'f="{}"; echo mv "$f" "${f%_*}_${CI_COMMIT_SHORT_SHA}.car"' \;
Remove echo once you are satisfied of the result.

List the files in which i'm replacing with sed

I have the command
find . -type f \( ! -name "*.png" \) -print0 | \
xargs -0 sed -i 's#word#replace#g'
awk/sed: How to do a recursive find/replace of a string?
BASH: recursive program to replace text in a tree of files
This command works so far but i want to show the files in which sed replaces text.
Is there some parameter which allows that?
You can use the print and exec options together and print out the files that it processes:
find . -type f \( ! -name "*.png" \) -print -exec sed -i 's#word#replace#g' {} \; 2>/dev/null
You are missing cmp to compare two files pre and post change. Something on these lines you could try:
find . -type f \( ! -name "*.png" \) -exec sh -c 'sed "s/word/replace/g" $0 > $0.$$; if ! cmp $0.$$ $0; then echo $0; mv $0.$$ $0; else rm $0.$$; fi' {} \;
find - in current directory all the files name not like *.png
exec - sed search and replace word by relace word in file found by find command and put it in temp file appended by process id
if !cmp statement - compare both the files new and old and if they are not same then print file name along with the output and at the end move temp file to orginal file if they are not same else delete the temp file.
I don't know what platform you are on, but this is a possibility. Change your xargs to run a shell so you can do multiple commands and then inside that shell, test if the file is newer than some arbitrary file you created at the start - i.e. it is changed
touch /tmp/go
find ... | xargs ... -I % sh -c 'sed "%"; [ "%" -nt /tmp/go ] && echo %'

need to rename many files in directory using sed and find

I would like to rename all files named *-6.0.dll with *-6.1.dll
I tried:
find . -name '*-6.0.dll*' -exec mv {} $(echo {} | sed -e 's/-6.0.dll/-6.1.dll/g') \;
but this didn't work; the file names didn't change.
Any ideas?
for x in *-6.0.dll; do y=$(echo $x | sed -e 's/-6\.0\.dll$/-6.1.dll/'); echo mv $x $y; done
Remove the echo once you are satisfied the results are correct.
use this:
find . -name '*-6.0.dll*' -exec sh -c 'mv {} $(echo {} | sed -e 's/\-6\.0\.dll/\-6\.1\.dll/g')' \;
an explanation of using the sh -c vs mv can be found here http://linuxplayer.org/2010/05/shell-programming-trap-batch-rename-with-find
I also modified your regex, some of the characters need to be escaped for proper matching.

Recursively go through directories and files in bash + use wc

I need to go recursively through directories. First argument must be directory in which I need to start from, second argument is regex which describes name of the file.
ex. ./myscript.sh directory "regex"
While script recursively goes through directories and files, it must use wc -l to count lines in the files which are described by regex.
How can I use find with -exec to do that? Or there is maybe some other way to do it? Please help.
Thanks
Yes, you can use find:
$ find DIR -iname "regex" -type f -exec wc -l '{}' \;
Or, if you want to count the total number of lines, in all files:
$ find DIR -iname "regex" -type f -exec wc -l '{}' \; | awk '{ SUM += $1 } END { print SUM }'
Your script would then look like:
#!/bin/bash
# $1 - name of the directory - first argument
# $2 - regex - second argument
if [ $# -lt 2 ]; then
echo Usage: ./myscript.sh DIR "REGEX"
exit
fi
find "$1" -iname "$2" -type f -exec wc -l '{}' \;
Edit: - if you need more fancy regular expressions, use -regextype posix-extended and -regex instead of -iname as noted by #sudo_O in his answer.