Escape the current file name "{}" of shell find -exec in the subcommand result "$()"? - sh

I want to run something like:
find . -type files -exec echo "touch -cmd '"$(date --utc -r '{}' +"%Y-%m-%d %H:%M:%S.%N +0000")"' "$(ls --quoting-style=shell '{}')
It doesn't seem to work since "{}" doesn't seem to be expanded in the $(). How could I do to make it work right?

Simplify your problem.
Write a shell script, and then get the shell script working. Then execute your shell script using find.
find . -type files -exec myScript '{}'

Related

in "find -exec cmd", how to capture output and stderr of cmd?

When I use a bash script line like
find path -exec cmd1 \{\} \; -exec cmd2 \{\} \; -exec echo "a" \;
then I would like to insert after cmd1 redirections like
1>file1 2>file2
in order to carch the outputs of cmd1.
In all location I have tried to insert these redirections, always a l l output is written to the files, even error messages of find.
And braces {...} as I usually use as delimiters give errors.
What is the right way.
Regards

Perl: edit file, not just output to shell

I found a little one-liner of perl code that will change the serial in my zone-files on my Bind server.
However it wont change the actual file, it just gives me the output directly to the shell.
This is what I run:
find . -type f -print0 | xargs -0 perl -e "while(<>){ s/\d+(\s*;\s*[sS]erial)/2015050466\1/; print; }"
This gives me the correct output to the shell and if I remove the print; at the end of the perl line nothing happens and I want it to actually change the files to the output I got.
I'm a total noob when it comes to Perl so this might be a simple fix so any answer would be appreciated.
I am assuming you want to replace the string inside the files found by find.
Command example below will change in-place (-i) any "foo" with "bar" for all *.txt files from curent directory.
find . -type f -name '*.txt' -print0 | xargs -0 perl -p -i -e 's/foo/bar/g;'
And for your question, you should be able to get it with this command:
find . -type f -print0 | xargs -0 perl -p -i -e 's/\d+(\s*;\s*[sS]erial)/2015050466\1/;'
Note: It is good habit to always use single quotes rather than double quotes. This is because inside double quotes, a \, $, etc. may be processed by the shell before passed to Perl. See Bash manual.

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.

sed to exclude directories

I try to replace many files at once with sed using * as filename. However it tries to process directories too, and gives error and terminates. Is there a simple way to overcome this?
I'm not sure exactly how you're using sed here but the normal way to process only regular files in UNIX is with the find command, something like:
find . -type f -exec sed 's/Hello/Goodbye/g' {} ';'
The type restricts you to regular files, not directories or FIFOs or any other sort of filesystem magic.
If you run man find on your system, you will see a plethora of other options you can use.
To springboard on paxdiablo's answer, I cobbled this alias together, and added it to my bash aliases as 'recursive sed': rsed :
rsed() {
[[ -z $2 ]] && echo "usage: ${FUNCNAME[0]} oldtext newtext" && return
command find . -type f -exec sed -i "s/${1}/${2}/g" {} \;
}
Result:
> cat test/file
Hello how are you?
> rsed "Hello how are you?" "Fine thanks"
> cat test/file
Fine thanks

xargs doesn't recognize bash aliases

I'm trying to run the following command:
find . -iname '.#*' -print0 | xargs -0 -L 1 foobar
where "foobar" is an alias or function defined in my .bashrc file (in my case, it's a function that takes one parameter). Apparently xargs doesn't recognize these as things it can run. Is there a clever way to remedy this?
Since only your interactive shell knows about aliases, why not just run the alias without forking out through xargs?
find . -iname '.#*' -print0 | while read -r -d '' i; do foobar "$i"; done
If you're sure that your filenames don't have newlines in them (ick, why would they?), you can simplify this to
find . -iname '.#*' -print | while read -r i; do foobar "$i"; done
or even just find -iname '.#*' | ..., since the default directory is . and the default action is -print.
One more alternative:
IFS=$'\n'; for i in `find -iname '.#*'`; do foobar "$i"; done
telling Bash that words are only split on newlines (default: IFS=$' \t\n'). You should be careful with this, though; some scripts don't cope well with a changed $IFS.
Using Bash you may also specify the number of args being passed to your alias (or function) like so:
alias myFuncOrAlias='echo' # alias defined in your ~/.bashrc, ~/.profile, ...
echo arg1 arg2 | xargs -n 1 bash -cil 'myFuncOrAlias "$1"' arg0
echo arg1 arg2 | xargs bash -cil 'myFuncOrAlias "$#"' arg0
Adding a trailing space to the command being aliased causes other aliased commands to expand:
alias xargs='xargs ' # aliased commands passed to xargs will be expanded
See this answer for more info:
https://stackoverflow.com/a/59842439/11873710
This doesn't work because xargs expects to be able to exec the program given as its parameter.
Since foobar in your case is just a bash alias or function there's no program to execute.
Although it involves starting bash for each file returned by find, you could write a small shell script thus:
#!/bin/bash
. $(HOME)/.bashrc
func $*
and then pass the name of that script as the parameter to xargs
I usually use find like this:
find . -iname '' -exec cmd '{}' \;
'{}' will get replaced with the filename, and \; is necessary to terminate the execution chain. However, if that doesn't work with your function, you might need to run it through bash:
find .. |sed -e "s/.*/cmd '&'/"|bash
Find prints each file on a line, sed just prefixes this with your command, and then pipe it to bash for execution. Skip the |bash first to see what will happen.
try
find . -iname '.#*' -print0 | xargs -0 -L 1 $(foobar)