FIND folders and MV (rename) them using FIND, GREP, XARGS, and AWK? - bash

I'm trying to move my LOG folders. Here is what I have so far.
cd archive
find .. -type d -name 'LOGS' | xargs -I '{}' mv {} `echo {} | awk -F/ 'NF > 1 { print $(NF - 1)"-LOGS"; }'`
Unfortunately --> echo {} | awk -F/ 'NF > 1 { print $(NF - 1)"-LOGS"; }' <-- evaluates immediately. So doesn't give me the file name that I would prefer.
mv ../app1/LOGS app1-LOGS
mv ../app2/LOGS app2-LOGS
Is there a way to do this in a single line?

Using xargs:
find .. -type d -name 'LOGS' |
xargs -I {} bash -c 'd="${1%/*}"; mv "$1" "${d##*/}-LOGS"' - {}
Or else you can do that like this using process substitution:
cd archive
while IFS= read -rd '' dir; do
d="${dir%/*}"
d="${d##*/}"
mv "$dir" "$d-LOGS"
done < <(find .. -type d -name 'LOGS' -print0)

Related

Bash move files and rename it as username_filename in a different folder

There are 4 user folders user1, user2, user3 and user4.
They all have music in their folder and I need to move these .mp4, .mkv and .mp3 files into the folder /tmp/Papierkorb
Also I need to rename it like, when user1 has a music file, it should change the name of the file into the name of the user_filename, from which user It comes from.
This is what I have now:
for file in $(find -type f -name *.mp3;find -type f -name *.mkv;find -type f -name *.mp4)
do
echo mv "$file" /tmp/Papierkorb$file;
done
This is what appears with echo:
root#ubuntu-VirtualBox:/home# bash script.sh
mv ./user2/music/hits.mp3 /tmp/Papierkorb./user2/music/hits.mp3
mv ./user4/hits/music.mp3 /tmp/Papierkorb./user4/hits/music.mp3
mv ./user1/lied1.mp3 /tmp/Papierkorb./user1/lied1.mp3
mv ./user1/lied1.mkv /tmp/Papierkorb./user1/lied1.mkv
mv ./user1/lied12.mp4 /tmp/Papierkorb./user1/lied12.mp4
mv ./user1/1lied12.mp4 /tmp/Papierkorb./user1/1lied12.mp4
mv ./user3/test/meinealben/testlied.mp4 /tmp/Papierkorb./user3/test/meinealben/testlied.mp4
When I remove the echo, it says, that the folder after Papierkorb doesn't exist. I also don't know anything how I rename it into the name of the user, from which user the file comes from.
With find you can group the -name predicates and use the -exec ... {} + construct:
for user in user1 user2 user3 user4
do
find "./$user" '(' -name '*.[mM][pP][34]' -o -name '*.[mM][kK][vV]' ')' -exec sh -c '
for file
do
mv "$file" "$0${file##*/}"
done
' "/tmp/Papierkorb/${user}_" {} +
done
Notes:
to make the code easier I used one find per username
the -exec sh -c '...' "/tmp/Papierkorb/${user}_" {} + is a little hackish but thanks to that you'll directly have value of /tmp/Papierkorb/${user}_ as $0 in the inline script
Using find and awk
#!/bin/bash
dir="/path/to/users"
# for all users
find "$dir" -maxdepth 2 -regex '.*\.\([mM][pP][34]\|[mM][kK][vV]\)'| \
awk -F/ '{print |"mv "$0" /tmp/Papierkorb/"$(NF-1)"_"$NF}'
# for some users
# using multiple find folders
find "$dir"/user1 "$dir"/user2 "$dir"/user3 -maxdepth 2 -regex '.*\.\([mM][pP][34]\|[mM][kK][vV]\)'| \
awk -F/ '{print |"mv "$0" /tmp/Papierkorb/"$(NF-1)"_"$NF}'
# using awk user filter
find "$dir" -maxdepth 2 -regex '.*\.\([mM][pP][34]\|[mM][kK][vV]\)'| \
awk -F/ '/user1|user2|user3/ {print |"mv "$0" /tmp/Papierkorb/"$(NF-1)"_"$NF}'
# using awk condition
find "$dir" -maxdepth 2 -regex '.*\.\([mM][pP][34]\|[mM][kK][vV]\)'| \
awk -F/ '{if($(NF-1)=="user1" || $(NF-1)=="user2" print |"mv "$0" /tmp/Papierkorb/"$(NF-1)"_"$NF}'
Using find and xargs
find "$dir"/user1 "$dir"/user2 -maxdepth 2 -regex '.*\.\([mM][pP][3-4]\|[mM][kK][vV]\)'| \
xargs -i sh -c 'mv "{}" /tmp/Papierkorb/$(basename $(dirname "{}"))_$(basename "{}")'

How to find and move all files matching given Beginning Of File (BOF) string?

How to move all files which content begins with foo to another folder with command line ?
I tried this to echo filenames when matching:
for f in *.txt; do if [ $(head -c5 $f) = "foo" ]; then echo $f; fi; done;
but I'm often getting this error:
-bash: [: too much arguments
Use a Perl one-liner in combination with find and xargs, like so:
echo foo > 1.txt
echo "foo\nbar" > 2.txt
echo "bar\nfoo" > 3.txt
mkdir foodir
find . -maxdepth 1 -name '[123].txt' -exec perl -lne 'print $ARGV if /^foo/; last;' {} \; | xargs -I{} mv {} foodir
find foodir -type f
# foodir/2.txt
# foodir/1.txt
Find with awk
find . -type f -exec awk -F \/ "NR < 6 && /foo/ { fnd=1 } END { if (fnd==1) { split(FILENAME,arr,\"/\");print \"mv -f \"FILENAME\" newdir/\"arr[length(arr)] } }" '{}' \;
Use awk to process the first 5 lines (NR < 6). Search for foo and if it exists, set a fnd variable to 1. At the end, if fnd is 1, print the mv command, using split to get the filename without the directories. Check that everything looks as expected and then run with:
find . -type f -exec awk -F \/ "NR < 6 && /foo/ { fnd=1 } END { if (fnd==1) { split(FILENAME,arr,\"/\");print \"mv -f \"FILENAME\" newdir/\"arr[length(arr)] } }" '{}' \; | bash

Rename files to unique names and move them into a single destination directory

i have 100s of directories with same filename of content.html along with other files.
I am trying to copy all these content.html files under 1 directory, but since they have same name, it overwrites each other
so how can i rename and move all these under 1 directory
Eg:
./0BD3D9D2-F8B1-4472-95C2-13319650A45C:
card.png content.html note.xhtml quickLook.png snippet.txt
./0EA34DB4-CD56-42BE-91DA-F631E44FB6E0:
card.png content.html note.xhtml quickLook.png related snippet.txt
./1A33F29E-3938-4C2F-BA99-6B98FD045742:
card.png content.html note.xhtml quickLook.png snippet.txt
command i tried:
rename content.html to content
find . -type f | grep content.html | while read f; do mv $f ${f/.html/}; done
append number to filename "content" to make it unique
find . -type f | grep content | while read f; do i=1; echo mv $f $f$i.html; i=i+1; done
MacBook-Pro$ find . -type f | grep content | while read f; do i=1; echo mv $f $f$i.html; i=i+1; done
mv ./0BD3D9D2-F8B1-4472-95C2-13319650A45C/content ./0BD3D9D2-F8B1-4472-95C2-13319650A45C/content1.html
mv ./0EA34DB4-CD56-42BE-91DA-F631E44FB6E0/content ./0EA34DB4-CD56-42BE-91DA-F631E44FB6E0/content1.html
mv ./1A33F29E-3938-4C2F-BA99-6B98FD045742/content ./1A33F29E-3938-4C2F-BA99-6B98FD045742/content1.html
once above step is successful, i should be able do this to achieve my desired output:
find . -type f | grep content | while read f; do mv $f ../; done
however, i am sure i can do this in 1 step command and also my step 2 is not working (incrementing i)
any idea why step2 is not working??
bash script:
#!/bin/bash
find . -type f -name content.html | while IFS= read -r f; do
name=$(basename $f)
((++i))
mv "$f" "for_content/${name%.*}$i.html"
done
replace for_content with your destination folder name
Suppose in your base directory, you create a folder named final for storing
content.html files, then do something like below
find . -path ./final -prune -o -name "content.html" -print0 |
while read -r -d '' name
do
mv "$name" "./final/content$(mktemp -u XXXX).html"
# mktemp with -u option just creates random characters, or it is just a dry run
done
At the end you'll get all the content.html files under ./final folder in the format contentXXXX.html where XXXX are random characters.
Note:-path ./final -prune -o in find prevents it from descending to our results folder.
The inode of the of the files should be unique and so you could use the following:
find $(pwd) -name "content.html" -printf %f" "%i" "%p"\n" | awk '{ system("mv "$3" <directorytomoveto>"$2$1) }'
I'd use something like this:
find . -type f -name 'test' | awk 'BEGIN{ cnt=0 }{ printf "mv %s ./output-dir/content_%03d.txt\n", $0, cnt++ }' | bash;
You can replace ./output-dir/ with your destination directory
Example:
[root#sl7-o2 test]# ls -R
.:
1 2 3 output-dir
./1:
test
./2:
test
./3:
test
./output-dir:
[root#sl7-o2 test]# find . -type f -name 'test' | awk 'BEGIN{ cnt=0 }{ printf "mv %s ./output-dir/content_%03d.txt\n", $0, cnt++ }' | bash;
[root#sl7-o2 test]# ls ./output-dir/
content_000.txt content_001.txt content_002.txt
You can use shopt -s globstar to grab all content.html files recursively and then use a loop to rename them:
#!/bin/bash
set -o globstar
counter=0
dest_dir=/path/to/destination
for f in **/content.html; do # pick up all content.html files
[[ -f "$f" ]] || continue # skip if not a regular file
mv "$f" "$dest_dir/content_$((++counter).html"
done

Bash function find and rm

I am trying to do a recursive grep and deleting files with less than a specified entry.
To be more clear, I have a directory of 400000 text files and in each file i have 10 items each starting with the >. Now the problem is that some of the files out of the 4000000 files have only 6-7 or 8-9 items starting with >.
So I wish to delete the files which have fewer than 10 items. I am using the recursive function, however i am not able to figure out how to add rm in the recursive way. What I have till now is:
find . -name "*.[txt]" -exec grep ">" -c {} \;
You can use -exec like this:
find . -name "*.txt" -exec bash -c '(( $(grep ">" -c "$1") <= 10 )) && rm "$1"' - '{}' \;
To avoid creating shell per file you can use:
while read -r f; do
(( $(grep ">" -c "$f") <= 10 )) && rm "$f"
done < <(find . -name "*.txt")
I would break it up into smaller steps:
find . -type f -exec grep -c '>' {} + |
awk -F: '$2 != 10 {print $1}' |
xargs echo rm
remove the "echo" if you're satisfied it's working
The awk step is fragile if you have any filenames containing ":"

Bash: how to pipe each result of one command to another

I want to get the total count of the number of lines from all the files returned by the following command:
shell> find . -name *.info
All the .info files are nested in sub-directories so I can't simply do:
shell> wc -l *.info
Am sure this should be in any bash users repertoire, but am stuck!
Thanks
wc -l `find . -name *.info`
If you just want the total, use
wc -l `find . -name *.info` | tail -1
Edit: Piping to xargs also works, and hopefully can avoid the 'command line too long'.
find . -name *.info | xargs wc -l
You can use xargs like so:
find . -name *.info -print0 | xargs -0 cat | wc -l
some googling turns up
find /topleveldirectory/ -type f -exec wc -l {} \; | awk '{total += $1} END{print total}'
which seems to do the trick
#!/bin/bash
# bash 4.0
shopt -s globstar
sum=0
for file in **/*.info
do
if [ -f "$file" ];then
s=$(wc -l< "$file")
sum=$((sum+s))
fi
done
echo "Total: $sum"
find . -name "*.info" -exec wc -l {} \;
Note to self - read the question
find . -name "*.info" -exec cat {} \; | wc -l
# for a speed-up use: find ... -exec ... '{}' + | ...
find . -type f -name "*.info" -exec sed -n '$=' '{}' + | awk '{total += $0} END{print total}'

Resources