bash: trouble with find and sed in a directory - macos

Pulling my hair out - somebody save me from an early Q-ball.
I have a folder with loads of powerpoint files and I want to change a substring in each title. All of them are of the form "lecture 2 2014.pptx" and I want to change "2014" to "2016".
Insider the directory I try commands like:
find . -name "*2014*" | xargs -0 sed -i 's/2014/2016/g'
to no avail. Any advice?
Edit my goal is to change the file name. "Lecture 2 2014.pptx" to "Lecture 2 2016.pptx"

rename s/2014/2016/ *2014.pptx
If your list is too long to expand by shell try:
find -name \*2014.pptx -exec rename s/2014/2016/ {} \;

rename was already mentioned. Be aware that there are two version floating around: one with the syntax
rename [options] expression replacement file...
and one with the syntax
rename s/old/new/ file...
As an alternative: a simple Bash loop with a regex extracting the "2014" from each file name, replacing it with "2016"
re='(.*)2014(.*)'
for fname in *2014*; do
[[ $fname =~ $re ]]
mv "$fname" "${BASH_REMATCH[1]}2016${BASH_REMATCH[2]}"
done

Related

Renaming multiple directories while keeping a part in the middle with varying suffix

I'm trying to change the name of multiple directories using bash, the names being structures like the following:
DRMAD_CA-12__MRBK01_237a8430 DRMAD_CA-17__MRBK10_766c3396
DRMAD_CA-103__MRBK100_c27a6c1c
The goal is the to keep the MRBK as well as the number following directly after it (MRBK###), but to get rid of of the rest. The pattern of the prefix is always the same (DRMAD_CA-###__), while the suffix is '_' followed by a combination of exactly 8 letters and digits. Tried sed, but can't seem to figure out the right pattern.
Seeing other posts on Stackoverflow, I've tired variations of
ls | while read file; do new=$( echo $file | sed 's/[^0-9]*\([^ ]*\)[^.]*\(\..*\)*MRBK\1\2/' ) mv "$file" "$new" done
But since I don't really understand the syntax of sed, it doesn't produce a usable result.
Use rename utility.
First, print the old and new names, but do not rename anything:
rename --dry-run 's/.*(MRBK\d+).*/$1/' *MRBK*
If OK, actually rename:
rename 's/.*(MRBK\d+).*/$1/' *MRBK*
Install rename, for example, using conda.
Using find:
find . -type d -regextype posix-extended -regex "^.*MRBK[[:digit:]]+.*$" | while read line
do
dir=$(dirname $line)
newfil=$(grep -Eo 'MRBK[[:digit:]]+' <<< $line)
mv "$line" "$dir/$newfil"
done

Sed & Mac OS Terminal: How to remove parentheses content from the first line of every file?

I am on Mac Os 10.14.6 and have a directory that contains subdirectories that all contain text files. Altogether, there are many hundreds of text files.
I would like to go through the text files and check for any content in the first line that is in parentheses. If such content is found, then the parentheses (and content in the parentheses) should be removed.
Example:
Before removal:
The new world (82 edition)
After removal:
The new world
How would I do this?
Steps I have tried:
Google around, it seems SED would be best for this.
I have found this thread, which provides SED code for removing bracketed content.
sed -e 's/([^()]*)//g'
However, I am not sure how to adapt it to work on multiple files and also to limit it to the first line of those files. I found this thread which explains how to use SED on multiple files, but I am not sure how to adapt the example to work with parentheses content.
Please note: As long as the solution works on Mac OS terminal, then it does not need to use SED. However, from Googling, SED seems to be the most suited.
I managed to achieve what you're after simply by using a bash script and sed together, as so:
#!/bin/bash
for filename in $PWD/*.txt; do
sed -i '' '1 s/([^()]*)//g' $filename
done
The script simply iterates over all the .txt files in $PWD (the current working directory, so that you can add this script to your bin and run it anywhere), and then runs the command
sed -ie '1 s/([^()]*)//g' $filename
on the file. By starting the command with the number 1 we tell sed to only work on the first line of the file :)
Edit: Best Answer
The above works fine in a directory where all contained objects are files, and not including directories; in other words, the above does not perform recursive search through directories.
Therefore, after some research, this command should perform exactly what the question asks:
find . -name "*.txt" -exec sed -i '' '1 s/([^()]*)//g' {} \;
I must iterate, and reiterate, that you test this on a backup first to test it works. Otherwise, use the same command as above but change the '' in order to control the creation of backups. For example,
find . -name "*.txt" -exec sed -i '.bkp' '1 s/([^()]*)//g' {} \;
This command will perform the sed replace in the original file (keeping the filename) but will create a backup file for each with the appended .bkp, for example test1.txt becomes test1.txt.bkp. This a safer option, but choose what works best for you :)
Good try,
The command you where looking for single line:
sed -E '1s|\([^\)]+\)||'
The command to replace each input file first line:
sed -Ei '1s|\([^\)]+\)||' *.txt
example:
echo "The new world (82 edition)" |sed -E '1s|\([^\)]+\)||'
The new world
Explanation
sed -Ei E option: the extended RegExp syntax, i option: for in-place file replacement
sed -Ei '1s|match RegExp||' for first line only, replace first matched RegExp string with empty string
\([^\)]+\) RegExp matching: start with (, [^\)]any char not ), + - more than once, terminate with )
Try:
# create a temporary file
tmp=$(mktemp)
# for each something in _the current directory_
for i in *; do
# if it is not a file, don't parse it
if [ ! -f "$i" ]; then continue; fi
# remove parenthesis on first line, save the output in temporary file
sed '1s/([^)]*)//g' "$i" > "$tmp"
# move temporary file to the original file
mv "$tmp" "$i"
done
# remove temporary file
rm "$tmp"

Transfer a path with space in bash

I'm trying to run a program on every file on a dir.
But there is spaces in the name of the file. For example, a file can be named «/my/good/path/MY - AWFUL, FILE.DOC»
And when I'm trying to send the path to my the other tool (a python script), I've got an error saying «MY» is not a existing file. :(
Here is my current bash code:
#!/usr/bin/bash
for file in $(find "/my/pash" -name "*.DOC")
do
newvar=`program "$file"`
done
So… where is my problem?
Thanks everyone :)
Some correct answers, but no explanations so far:
a for loop is intended to iterate over words not lines. The given (unquoted) string is subject to word splitting (which is what is troubling you) and filename expansion, and then you iterate over the resulting words. You could set IFS to contain only a newline. The safest way is to use find -print0 and xargs -0 as demonstrated by Vytenis's answer
find -name "*.DOC" -print0 | xargs -r -0 -n1 program
#!/usr/bin/bash
find "/my/pash" -name "*.DOC" | while read file; do
newvar="$(program "$file")"
done
Note that this only fixes the case where a space or tab is in the file name. If you have a newline in the file name, it gets a little more complicated.
That is because the for loop will take every word inside the result of the find as an element to iterate over. for will see it as:
for file in {/my/good/path/MY, -, AWFUL, FILE.DOC}
echo "$file"
done
And will print:
/my/good/path/MY
-
AWFUL,
FILE.DOC
One solution to this problem is to use the xargs program to pass the result of the find as your python program argument:
find "/my/pash" -name "*.DOC" -print0 | xargs -0 -i program "{}"
the loop treats blanks as delimiter, so try this one:
find "/my/pash" -name "*.DOC" | while read file; do
newvar=`program "$file"`
done

rename fails on filenames with spaces

I have a KSH simple script that creates a list of files we receive on our FTP server.
sF_Array=(` find . -type f ... `)
sF_len=${#sF_Array[*]}
for name in ${sF_Array[#]}
do
basename "$name" # This removes the leading slashdot
done>$ddw_data/FTP_FILE_LIST
Challenge is that some of the files have spaces in them (Files are coming to us from MS Windows machines) so I used rename
for name in ${sF_Array[#]}
do
rename 's/ /_/g' "$name" # This removes spaces in file names
basename "$name" # This removes the leading slashdot
done>$ddw_data/FTP_FILE_LIST
but it does not to work. This is what FTP_FILE_LIST looks like (notice 2nd and 3rd records/filenames):
TCA~PRE~PREP~9939985~ONTDTVE3A~33~F~3922~R22-100~000806451655~20130819~040320.XML
J
MEM~ETT~CVT~654687-MEMTO~jgm16227~1~P~1100~R24-500~033068658187~20130813~234639.XML
J
MEM~FUN~TEST~312326-MEMTO~jgm16227~2~P~1100~R24-200~035696412416~20130813~234638.XML
J-MEM~DCA~FVT~5333131~ONTDTVD1C~1~F~3420~DECA1MR0-01~XED1B1201A3313~20130827~201916.XML
TCA~COS~COSM~95518585~ONTDTVE7A~63~F~3911~R22-300~00065881346~20130817~000300.XML
I want FTP_FILE_LIST to look like this instead (notice 2nd and 3rd records/filenames):
TCA~PRE~PREP~9939985~ONTDTVE3A~33~F~3922~R22-100~000806451655~20130819~040320.XML
J_MEM~ETT~CVT~654687-MEMTO~jgm16227~1~P~1100~R24-500~033068658187~20130813~234639.XML
J_MEM~FUN~TEST~312326-MEMTO~jgm16227~2~P~1100~R24-200~035696412416~20130813~234638.XML
J-MEM~DCA~FVT~5333131~ONTDTVD1C~1~F~3420~DECA1MR0-01~XED1B1201A3313~20130827~201916.XML
TCA~COS~COSM~95518585~ONTDTVE7A~63~F~3911~R22-300~00065881346~20130817~000300.XML
What am I missing??
rename will rename the actual file, not change the variable. Use tr or something to fix the file names inline:
basename "$(echo "$name" | tr " " "_")"
I resolved my problem by creating two separate tasks in my script:
1) to rename files with spaces
find . -type f \( ...\) | while read file
do
target=`echo "$file" | sed 's/ /_/g'`
mv "$file" "$target"
done
2) to create my file list
sF_Array=(` find . -type f ... `)
sF_len=${#sF_Array[*]}
for name in ${sF_Array[#]}
do
basename "$name" # This removes the leading slashdot
done>$ddw_data/FTP_FILE_LIST
It's interesting that it works this way but does not in one single step.
Thanks for your time.

How can I process a list of files that includes spaces in its names in Unix?

I'm trying to list the files in a directory and do something to them in the Mac OS X prompt.
It should go like this: for f in $(ls -1); do echo $f; done
If I have files without spaces in their names (fileA.txt, fileB.txt), the echo works fine.
If the files include spaces in their names ("file A.txt", "file B.txt"), I get 4 strings (file, A.txt, file, B.txt).
I've tried quoting the listing command, but it only changed the problem.
If I do this: for f in $(ls -1); do echo $f; done
I get: file A.txt\nfile B.txt
(It displays correctly, but it is a single string and I need the 2 lines separated.
Step away from ls if at all possible. Use find from the findutils package.
find /target/path -type f -print0 | xargs -0 your_command_here
-print0 will cause find to output the names separated by NUL characters (ASCII zero). The -0 argument to xargs tells it to expect the arguments separated by NUL characters too, so everything will work just fine.
Replace /target/path with the path under which your files are located.
-type f will only locate files. Use -type d for directories, or omit altogether to get both.
Replace your_command_here with the command you'll use to process the file names. (Note: If you run this from a shell using echo for your_command_here you'll get everything on one line - don't get confused by that shell artifact, xargs will do the expected right thing anyway.)
Edit: Alternatively (or if you don't have xargs), you can use the much less efficient
find /target/path -type f -exec your_command_here \{\} \;
\{\} \; is the escape for {} ; which is the placeholder for the currently processed file. find will then invoke your_command_here with {} ; replaced by the file name, and since your_command_here will be launched by find and not by the shell the spaces won't matter.
The second version will be less efficient since find will launch a new process for each and every file found. xargs is smart enough to pipe the commands to a newly launched process if it can figure it's safe to do so. Prefer the xargs version if you have the choice.
for f in *; do echo "$f"; done
should do what you want. Why are you using ls instead of * ?
In general, dealing with spaces in shell is a PITA. Take a look at the $IFS variable, or better yet at Perl, Ruby, Python, etc.
Here's an answer using $IFS as discussed by derobert
http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
You can pipe the arguments into read. For example, to cat all files in the directory:
ls -1 | while read FILENAME; do cat "$FILENAME"; done
This means you can still use ls, as you have in your question, or any other command that produces $IFS delimited output.
The while loop makes it much easier to do several things to the argument, and makes complex processing more readable in my opinion. A contrived example:
ls -1 | while read FILE
do
echo 1: "$FILE"
echo 2: "$FILE"
done
look --quoting-style option.
for instance, --quoting-style=c would produce :
$ ls --quoting-style=c
"file1" "file2" "dir one"
Check out the manpage for xargs:
it works like this:
ls -1 /tmp/*.jpeg | xargs rm

Resources