I have around 1000 files (png) and need to move them into the corresponding directory and their sub-directory.
I do have 26 directories (A - Z) and below each directory the complete alphabet A-Z again. File names are 6 characters/digits long and have a png extension, e.g. e.g. AH2BC0.png
I would need to move the file AH2BC0.png into the directory A and within that directory into the sub-directory H, e.g.A->H->AH2BC0.png.
I have created following script which is not really working as expected:
#!/bin/bash
ls >LISTE.txt
for i in LISTE.txt; do
a=$(cat $i | cut -b 1 | tr '[:lower:]' '[:upper:]')
b=$(cat $i | cut -b 2 | tr '[:lower:]' '[:upper:]')
mkdir -p $a/$b
cat $i | xargs mv $a/$b
rm $i
done
Problem is that a) the sub-directory is not created and b) the files are not moved. Any suggestions or better ideas for the script?
Thanks
PS: I guess it's obvious that it's quite some years ago that I have created any bash scripts or coded so please bear with me.
PSS: working on MAC OSX bash 3.2
There's already a post showing a better program to do what you want but I thought I'd show you how to fix yours. Hopefully you'll find it informative.
#!/bin/bash
ls >LISTE.txt
for i in LISTE.txt; do
This loops over the single value LISTE.txt; replace it with:
for i in $(cat LISTE.txt); do
to loop over the contents of the file instead.
a=$(cat $i | cut -b 1 | tr '[:lower:]' '[:upper:]')
b=$(cat $i | cut -b 2 | tr '[:lower:]' '[:upper:]')
You want to use echo rather than cat in the above two lines, as you're after the name of the file not its content.
mkdir -p $a/$b
cat $i | xargs mv $a/$b
I don't think the above line does what you think it does... It will attempt to rename the $a/$b directory to C, where C is the content of file $i. Replace it with:
mv $i $a/$b
The following line is not needed:
rm $i
So simply delete it. It would only be necessary if you copied rather than moved the files using mv.
done
Here's your complete program after the changes I've suggested.
#!/bin/bash
ls >LISTE.txt
for i in $(cat LISTE.txt); do
a=$(echo $i | cut -b 1 | tr '[:lower:]' '[:upper:]')
b=$(echo $i | cut -b 2 | tr '[:lower:]' '[:upper:]')
mkdir -p $a/$b
mv $i $a/$b
done
#!/bin/bash
for item in *; do
first=${item:0:1}
second=${item:1:1}
folder="$first/$second"
mkdir -p $folder
mv $item $folder/
done
Related
I need bash script, these are my files:
./2019-01-11_15-00-29_UTC.mp4
./2019-02-10_17-42-18_UTC.mp4
./2019-01-03_14-45-43_UTC.mp4
./2018-12-24_13-00-32_UTC.mp4
./2018-12-09_19-50-59_UTC.mp4
./2019-01-11_14-51-08_UTC.mp4
./2019-01-06_16-41-54_UTC.mp4
./2019-02-03_10-33-33_UTC.mp4
./2019-02-16_18-21-30_UTC.mp4
wanna make two folder 2018 & 2019 then move files own folder.
I use this code:
ls *.mp4 | awk -F"-" '{print $1}' | while read day file ; do mkdir -p "$day"; mv "$file" "$day"; done
It makes folder but not move
Aren't you getting errors? When I run it I get
mv: cannot stat '': No such file or directory
once for each file. The reason is that file isn't being set in your loop.
ls *.mp4 | awk -F"-" '{print $1}'
Will generate a list of years
2018
2018
2019
2019
2019
2019
2019
2019
2019
That's a single column of data.
while read day file
reads the year into day (day?) and since there's no more data, leaves file empty.
mkdir -p "$day"
works fine, but
mv "$file" "$day"
evaluates to
mv "" "2018"
Try this.
for f in *.mp4
do mkdir -p "${f%%-*}" && mv "$f" "${f%%-*}"
done
The ${f%%-*} just returns $f with everything from the first dash removed. The result:
$: find
.
./2018
./2018/2018-12-09_19-50-59_UTC.mp4
./2018/2018-12-24_13-00-32_UTC.mp4
./2019
./2019/2019-01-03_14-45-43_UTC.mp4
./2019/2019-01-06_16-41-54_UTC.mp4
./2019/2019-01-11_14-51-08_UTC.mp4
./2019/2019-01-11_15-00-29_UTC.mp4
./2019/2019-02-03_10-33-33_UTC.mp4
./2019/2019-02-10_17-42-18_UTC.mp4
./2019/2019-02-16_18-21-30_UTC.mp4
You can use xargs to achieve this.
ls *.mp4 | xargs -I{} sh -c 'folder=`echo {} | cut -d"-" -f1`;mkdir -p $folder;mv {} $folder/'
Here, the entire file names are sent to xargs and, for each file the folder name is obtained using the cut command. Then the file is moved into the created folder.
More about xargs: http://man7.org/linux/man-pages/man1/xargs.1.html
Edited
for file in *.mp4 ; do
date=$(echo $file | cut -d'_' -f1)
year=$(echo $date | cut -d'-' -f1)
month=$(echo $date | cut -d'-' -f2)
day=$(echo $date | cut -d'-' -f3)
mkdir -p $year/$month/$day
mv $file $year/$month/$day/
done
I have a holiday folder with all pictures unsorted in the same folder, all files are tagged with the year. I want to create a folder for the year. This I've solved with:
#!/bin/bash
SOURCE=/path/to/pictures
cd ~/pictures/sorted.by.year/
ls $SOURCE | grep -o -P '(?<!\d)\d{4}(?!\d)' | tr '/' '\n' | sort |
uniq | xargs mkdir
Now to my problem, I want to create a symlink to all year folders from the filename so it will create symlinks, like this:
~/pictures/sorted.by.year/2014/symlink-picture.on.sister.2014.jpg
~/pictures/sorted.by.year/2012/symlink-picture.on.dad.2012.jpg
I have tried with xargs but the result was not as I wanted in the example above.
Does someone have an idea how to do it?
Please check this script, I added some more line after your script:
#!/bin/bash
SOURCE=/path/to/pictures
cd ~/pictures/sorted.by.year/
ls $SOURCE | grep -o -P '(?<!\d)\d{4}(?!\d)' | tr '/' '\n' | sort | uniq | xargs mkdir
for file in /path/to/pictures/*
do
if [[ -d $file ]]; then # Skip directory
continue;
fi
DIRNAME=$(echo "$file" | grep -o -P '(?<!\d)\d{4}(?!\d)');
ln -s "$file" "./$DIRNAME/"
done
I've been trying to write a shell script for a while and am just stumped. I'm on a mac and have some knowledge of using UNIX but I'm stumped. I have a list of files:
folder1_123
folder1_abc
folder2_654
folder2_zxy
and I want them to be like this:
folder1/123
folder1/abc
folder2/654
folder2/zxy
so far I've gotten a script that looks like this:
for file in *_.*; do
dir=${file%%.*}
mkdir -p "$dir"
mv "$file" "$dir"
done
Check below one liner script for the same. Run this script in the directory containing all files in the format _. It will iterate over each file name will take first part before _ and will create a directory with that name.After that it will move the file from parent directory to newly created directory,until its done with all files.
for i in `ls |grep _`;do mkdir -p `echo $i|cut -f1 -d "_"`;mv $i `echo $i|cut -f1 -d "_"`/`echo $i|cut -f2 -d "_"`;done
You can split file/dir name with 'cut':
for file in *_*; do
DIRNAME=$(echo $file | cut -d"_" -f1)
if [[ ! -d ${DIRNAME} ]]; then
mkdir ${DIRNAME}
fi
FILENAME=$(echo $file | cut -d"_" -f2)
mv $file ${DIRNAME}/${FILENAME}
done
The body of your loop is wrong. It should be:
dir=${file%_*}
newfn=${file#*_}
mkdir -p $dir && mv $file $dir/$newfn
Depending on your requirements, you might or might not want to add a guard
if [[ -f $file ]]
then
...
fi
around this.
I'm trying to create a script that gets an extension and reports in two columns, the user and the amount of files that user owns with that extension.
The results must be printed in report.txt
Here is my code.
#!/bin/bash
#Uncoment to create /tmp/test/ with 50 txt files
#mkdir /tmp/test/
#touch /tmp/test/arch{01..50}.txt
clear
usage(){
echo "The script needs an extension to search"
echo "$0 <extension>"
}
if [ $# -eq 0 ]; then
usage
exit 1
fi
folder="/tmp/test/"
touch report.txt
count=0
pushd $folder
for file in $(ls -l); do
grep "*.$1" | cut -d " " -f3 >> report.txt
done
popd
The program just runs endlessly. And I'm not even counting the files for each user.
How can I solve this using only grep and cut?
With GNU stat :
stat -c '%U' *."$1" | sort | uniq -c | awk '{print $2,"\t",$1}' > report.txt
As pointed out by mklement0, under BSD/OSX you must use a -f option with stat :
stat -f '%Su' *."$1" | sort | uniq -c | awk '{print $2,"\t",$1}' > report.txt
Edit :
To process many files and avoid argument number limitation, you'd better use a printf piped to the stat command (thanks again mklement0) :
printf '%s\0' *."$1" | xargs -0 stat -c '%U' | sort | uniq -c | awk '{print $2,"\t",$1}'
You don't need a loop for this (unless you later need to loop over several folders), and changing the working directory in a script is rarely necessary. Also, reading ls output is usually not recommended.
Here's a version that replaces the loop, and uses du:
ext="$1"
printf "Folder '%s':\t" "$folder" >>report.txt
du -hc "$folder"/*."$ext" | sed -n '$p' >>report.txt
I am trying to write a script that will find the most recently added item from my downloads folder and move to to another folder. I'm close but stuck on the final part. I'm doing this as an exercise to better learn iTerm2, not for practical reasons. I realize there are simpler ways to do this in browser.
ls -t1 /Users/name/downloads | head -n 1 | > Users/name/targetfolder
If the item is a file, you can pipe your head command to cp :
ls -t1 /Users/name/downloads | head -n 1 | xargs cp -t Users/name/targetfolder
You may also add a test to check whether the item is a file or a directory :
last=$(ls -t1 . | head -n 1)
todir=Users/name/targetfolder
[ -d $last ] && cp -r "$last" "$todir" || cp "$last" "$todir"
You are correctly finding the most recent item with:
ls -t1 /Users/name/downloads | head -n 1
However you are making mistake afer that.
What you can do is:
mv $(ls -t1 /Users/name/downloads | head -n 1) Users/name/targetfolder
Above is a standard mv command whose syntax is:
mv filename target_filename # if you are renaming a file. Or,
mv filename target_dirname # if moving the file to a different directory.
Anything command between $() is replaced by its output.
So $(ls -t1 /Users/name/downloads | head -n 1) is replaced by the most recent file.
Hence, basically, the command means mv most_recent_file Users/name/targetfolder