Search and replace with grep and perl - perl

I need to replace "vi_chiron" with "vi_huracan" from all the files below. I am using the following command line and also have changed the permission to full access for all the underlying files:
grep -ri "vi_chiron" ./ | grep -v Header | xargs perl -e "s/vi_chiron/vi_huracan/" -pi
I am getting the error:
"Can't open ./build/drivers_file.min:#: No such file or directory.
" and many other similar errors. Any idea why ? Below is the permission for the file:
ll build/drivers_file.min
-rwxrwxrwx 1 ask vi 5860 Mar 13 12:07 build/drivers_file.min

You can change your command in the following ways:
grep -ril "vi_chiron" . | xargs grep -vl Header | xargs perl -e "s/vi_chiron/vi_huracan/" -pi
or
find . -type f -exec grep -ril "vi_chiron" | xargs grep -vl Header | xargs perl -e "s/vi_chiron/vi_huracan/" -pi
In order to manipulate all files that do not contain Header and change vi_chiron into vi_hurican.
If I were you, I would simplify the chain and do it the other way around:
grep -rvl Header . | xargs sed -i.bak 's/vi_chiron/vi_hurican/'
If you are confident enough change the -i.bak into -i in order to have sed not taking any backups.
Also note that your change and replace is not global, if you want to put it globally use: sed -i.bak 's/vi_chiron/vi_hurican/g' instead.
'-i[SUFFIX]' '--in-place[=SUFFIX]'
This option specifies that files are to be edited in-place. GNU
'sed' does this by creating a temporary file and sending output to
this file rather than to the standard output.(1).
This option implies '-s'.
When the end of the file is reached, the temporary file is renamed
to the output file's original name. The extension, if supplied, is
used to modify the name of the old file before renaming the
temporary file, thereby making a backup copy(2)).
This rule is followed: if the extension doesn't contain a '*', then
it is appended to the end of the current filename as a suffix; if
the extension does contain one or more '*' characters, then _each_
asterisk is replaced with the current filename. This allows you to
add a prefix to the backup file, instead of (or in addition to) a
suffix, or even to place backup copies of the original files into
another directory (provided the directory already exists).
If no extension is supplied, the original file is overwritten
without making a backup.
source: https://www.gnu.org/software/sed/manual/sed.txt

Related

How to rename all the files (without for loop) in a single line command?

I want to rename all the files in my home directory (example abc), in the format (abc_bkp) without using any loops and it should be a single line command in unix (bash script).
If the directory contains nothing but files, this should do it:
ls | xargs -I {} mv {} {}_bkp
If it contains subdirectories, links, and other things you don't want to rename, you must filter the output of ls. Here is a crude way to do it; maybe someone can suggest a more elegant approach:
ls -l | grep ^- | cut -d' ' -f 13 | xargs -I {} mv {} {}_bkp
If you don't want to use loops then I believe the BEST way could be find command, try following command as a DRY run first and once you are satisfy with results then you could remove echo from it to give a real shot.
find -type f -or -type d | xargs -I % echo mv % %_bkp
-I: From man xargs page:
-I replace-str
Replace occurrences of replace-str in the initial-arguments with names read from standard input. Also, unquoted blanks do not
terminate
input items; instead the separator is the newline character. Implies -x and -L 1.

Using sed and mv to add characters to files

First off, I'd like to say that I know this is almost an exact duplicate of some posts that I've read, but have not had any luck with referencing.
I have 100+ files that all follow a very strict naming convention of 5_##_<name>.ext My issue was that when originally making these files I failed to realise that 5_100_ and above would mess up my ordering.
I am now trying to append a 0 in front of every number between 01 and 99. I've written a bash script using sed that works for the file contents (the file name is in the file as well):
#!/bin/bash
for fl in *.tcl; do
echo Filename: $fl
#sed -i 's/5_\(..\)_/5_0\1_/g' $fl
done
However, this only changes the contents and not the filename itself. I've read that mv is the solution (rename is simpler but I do not have it on my system). My current incarnation of my multiple attempts is:
mv "$fl" $(echo "$file" | sed -e 's/5_\(..\)_/5_0\1_/g') but it gives me an error: mv: missing destination file operand after <filename>
Again, I'm sorry about the duplicate but I wasn't able to solve my issue by reading it. I'm sure I'm just using the combination of mv and sed incorrectly.
Solution was entered in the comments. I was using $file instead of $fl.
Something like this might be useful:
for n in $(seq 99)
do
prefix2="5_$(printf "%02d" ${n})_"
prefix3="5_$(printf "%03d" ${n})_"
for f in ${prefix2}*.tcl
do
suffix="${f#${prefix2}}"
[[ -r "${prefix3}${suffix}" ]] || mv "${prefix2}${suffix}" "${prefix3}${suffix}"
done
done
Rather than processing every single file, it only looks at the ones that currently have a "5_XX_" prefix, and only renames them if the corresponding "5_XXX_" file doesn't already exist...
#!/bin/bash
for fl in *.tcl
do
NewName="$(echo "${fl} | sed '/^5_[0-9]\{2\}_/ s/../&0/' )"
#echo "Filename: ${fl} -> ${NewName}
[ ! "${fl}" = "${NewName}" ] && mv ${fl} ${NewName}
done
With a bit a securisation a allow to pass several time on same folder (changing only needed one).
Under linux (non posix sed by default), use sed --posix instead of simple sed call

sed with filename from pipe

In a folder I have many files with several parameters in filenames, e.g (just with one parameter) file_a1.0.txt, file_a1.2.txt etc.
These are generated by a c++ code and I'd need to take the last one (in time) generated. I don't know a priori what will be the value of this parameter when the code is terminated. After that I need to copy the 2nd line of this last file.
To copy the 2nd line of the any file, I know that this sed command works:
sed -n 2p filename
I know also how to find the last generated file:
ls -rtl file_a*.txt | tail -1
Question:
how to combine these two operation? Certainly it is possible to pipe the 2nd operation to that sed operation but I dont know how to include filename from pipe as input to that sed command.
You can use this,
ls -rt1 file_a*.txt | tail -1 | xargs sed -n '2p'
(OR)
sed -n '2p' `ls -rt1 file_a*.txt | tail -1`
sed -n '2p' $(ls -rt1 file_a*.txt | tail -1)
Typically you can put a command in back ticks to put its output at a particular point in another command - so
sed -n 2p `ls -rt name*.txt | tail -1 `
Alternatively - and preferred, because it is easier to nest etc -
sed -n 2p $(ls -rt name*.txt | tail -1)
-r in ls is reverse order.
-r, --reverse
reverse order while sorting
But it is not good idea when used it with tail -1.
With below change (head -1 without r option in ls), performance will be better, that you needn't wait to list all files then pipe to tail command
sed -n 2p $(ls -t1 name*.txt | head -1 )
I was looking for a similar solution: taking the file names from a pipe of grep results to feed to sed. I've copied my answer here for the search & replace, but perhaps this example can help as it calls sed for each of the names found in the pipe:
this command to simply find all the files:
grep -i -l -r foo ./*
this one to exclude this_shell.sh (in case you put the command in a script called this_shell.sh), tee the output to the console to see what happened, and then use sed on each file name found to replace the text foo with bar:
grep -i -l -r --exclude "this_shell.sh" foo ./* | tee /dev/fd/2 | while read -r x; do sed -b -i 's/foo/bar/gi' "$x"; done
I chose this method, as I didn't like having all the timestamps changed for files not modified. Feeding the grep result allows only the files with target text to be looked at (thus likely may improve performance / speed as well)
be sure to backup your files & test before using. May not work in some environments for files with embedded spaces. (?)
fwiw - I had some problems using the tail method, it seems that the entire dataset was generated before calling tail on just the last item.

unix find and replace text in dir and subdirs

I'm trying to change the name of "my-silly-home-page-name.html" to "index.html" in all documents within a given master directory and subdirs.
I saw this: Shell script - search and replace text in multiple files using a list of strings.
And this: How to change all occurrences of a word in all files in a directory
I have tried this:
grep -r "my-silly-home-page-name.html" .
This finds the lines on which the text exists, but now I would like to substitute 'my-silly-home-page-name' for 'index'.
How would I do this with sed or perl?
Or do I even need sed/perl?
Something like:
grep -r "my-silly-home-page-name.html" . | sed 's/$1/'index'/g'
?
Also; I am trying this with perl, and I try the following:
perl -i -p -e 's/my-silly-home-page-name\.html/index\.html/g' *
This works, but I get an error when perl encounters directories, saying "Can't do inplace edit: SOMEDIR-NAME is not a regular file, <> line N"
Thanks,
jml
find . -type f -exec \
perl -i -pe's/my-silly-home-page-name(?=\.html)/index/g' {} +
Or if your find doesn't support -exec +,
find . -type f -print0 | xargs -0 \
perl -i -pe's/my-silly-home-page-name(?=\.html)/index/g'
Both pass to Perl as arguments as many names at a time as possible. Both work with any file name, including those that contains newlines.
If you are on Windows and you are using a Windows build of Perl (as opposed to a cygwin build), -i won't work unless you also do a backup of the original. Change -i to -i.bak. You can then go and delete the backups using
find . -type f -name '*.bak' -delete
This should do the job:
find . -type f -print0 | xargs -0 sed -e 's/my-silly-home-page-name\.html/index\.html/g' -i
Basically it gathers recursively all the files from the given directory (. in the example) with find and runs sed with the same substitution command as in the perl command in the question through xargs.
Regarding the question about sed vs. perl, I'd say that you should use the one you're more comfortable with since I don't expect huge differences (the substitution command is the same one after all).
There are probably better ways to do this but you can use:
find . -name oldname.html |perl -e 'map { s/[\r\n]//g; $old = $_; s/oldname.txt$/newname.html/; rename $old,$_ } <>';
Fyi, grep searches for a pattern; find searches for files.

Why do I have to specify the -i switch with a backup extension when using ActivePerl?

I cannot get in-place editing Perl one-liners running under ActivePerl to work unless I specify them with a backup extension:
C:\> perl -i -ape "splice (#F, 2, 0, q(inserted text)); $_ = qq(#F\n);" file1.txt
Can't do inplace edit without backup.
The same command with -i.bak or -i.orig works a treat but creates an unwanted backup file in the process.
Is there a way around this?
This is a Windows/MS-DOS limitation. According to perldiag:
You're on a system such as MS-DOS that gets confused if you try reading from a deleted (but still opened) file. You have to say -i.bak, or some such.
Perl's -i implementation causes it to delete file1.txt while keeping an open handle to it, then re-create the file with the same name. This allows you to 'read' file1.txt even though it has been deleted and is being re-created. Unfortunately, Windows/MS-DOS does not allow you to delete a file that has an open handle attached to it, so this mechanism does not work.
Your best shot is to use -i.bak and then delete the backup file. This at least gives you some protection - for example, you could opt not to delete the backup if perl exits with a non-zero exit code. Something like:
perl -i.bak -ape "splice...." file1.txt && del file1.bak
Sample with recursive modify and delete both done by find. Works on e.g. mingw git bash on windows.
$ find . -name "*.xml" -print0 | xargs -0 perl -p -i.bak -e 's#\s*<property name="blah" value="false" />\s*##g'
$ find . -name "*.bak" -print0 | xargs -0 rm
Binary terminated values passed between find/xargs to handle spaces. Unusual s/ prefix to avoid mangling xml in search term. This assumes you didn't have any .bak files hanging around to begin.