How to change multiple directories' name by computation on the numbers in the names? - shell

I have some directories. Their names are as follows,
s1_tw
s2_tw
s3_tw
s4_tw
How to change their names by add a fixed integer to the number following "s"? How can I change the directories' name to
s22_tw
s23_tw
s24_tw
s25_tw
by changing s1 to s22 (1 + 21 = 22), s2 to s23, etc? (Here adding 21 is expected)
I tried with
for f in s*_tw
do
for i in `seq 1 1 4`
do
mv -i "${f//s${i}/s(${i}+21)}"
done
done
But I know it is not correct, because I can not perform addition operation in this command. Could you please give some suggestions?

This will rename your directories:
#!/bin/bash
find . -maxdepth 1 -type d -name "s?_tw" -print0 | while IFS= read -r -d '' dir
do
digit=$(echo "$dir" | sed 's#./s\([0-9]\)_tw#\1#')
echo "DIGIT=$digit"
(( newdigit = digit + 21 ))
echo "NEWDIGIT=$newdigit"
mv "$dir" "s${newdigit}_tw"
done
The find -print0 with while and read comes from https://mywiki.wooledge.org/BashFAQ/001.

Related

Create a new folder named the next count

I have a folder of projects, which labels projects based on a number. It starts at 001 and continues counting. I have a bash script I run through Alfred, however, I currently have to type the name of the folder.
QUERY={query}
mkdir /Users/admin/Documents/projects/"$QUERY"
I would like to have the script automatically name the folder to the next number.
For example, if the newest folder is "019" then I would like it to automatically name it to "020"
This is what I've whipped up so far:
nextNum = $(find ~/documents/projects/* -maxdepth 1 -type d -print| wc -l)
numP = nextNum + 1
mkdir /Users/admin/Documents/projects/00"$numP"
I'm not sure if my variable syntax is correct, or if variables are the best way to do this. I am a complete noob to bash so any help is appreciated.
This might be what you're looking for:
#!/bin/bash
cd /Users/admin/Documents/projects || exit
if ! [[ -d 000 ]]; then mkdir 000; exit; fi
dirs=([0-9][0-9][0-9])
mkdir $(printf %03d $(( 1 + 10#${dirs[${#dirs[*]} - 1]} )) )
See comments in code to understand the code.
#!/bin/bash
# Configure your project dir
projects_dir=/Users/admin/Documents/projects
# Find the last project number.
# You can use wc instead of the sort|tail|sed I used. The result is the same.
last_project=$(\ls $projects_dir | sort -n | tail -n1 | sed 's/^0*//')
# Use printf to add the leading '0'
# ${last_project:-0} will substitute '0' when $last_project not set,
# therefore it will work even if your project directory is empty.
# $(( expr )) evaluates math expression. Also you can not have
# subshell expression in $(( expr )) so you can't substitute
# $last_project with the expression above
mkdir $projects_dir/$(printf "%03d" $((${last_project:-0} + 1)))
Although unlikely, the issue with the above script is that hitting a project count over 999 will break the 3 digits directory name convention. A simple fix is to use a 4 digits convention. A directory large amount of files/subdirectories is not recommended anyway so if you do reach 9999 projects, it's best to continue in a second project directory.
Give a try to this funny one:
projects_dir="/Users/admin/Documents/projects/"
[[ -n "${HOME}" ]] && projects_dir="${projects_dir/#~/$HOME}"
mkdir "${projects_dir}"/$(printf "%03d" $(find "${projects_dir}" -maxdepth 1 -type d -printf . | wc -c))
(EDIT)
Expanded version with bash trace output:
set -x
projects_dir="/Users/admin/Documents/projects/"
[[ -n "${HOME}" ]] && projects_dir="${projects_dir/#~/$HOME}"
cd "${projects_dir}" || exit 1
printf "each dot is a directory: "
find "${projects_dir}" -maxdepth 1 -type d -printf .
printf "\n"
let next_directory_id=$(find "${projects_dir}" -maxdepth 1 -type d -printf . | wc -c)
next_directory_name=$(printf "%03d" "${next_directory_id}")
mkdir "${projects_dir}"/"${next_directory_name}"
set +x
targ_number=$(find ~/documents/projects/ -maxdepth 1 -type d -print| wc -l)
mkdir /Users/admin/Documents/projects/$(printf "%03d" $targ_number)

Bash Script to Prepend a Single Random Character to All Files In a Folder

I have an audio sample library with thousands of files. I would like to shuffle/randomize the order of these files. Can someone provide me with a bash script/line that would prepend a single random character to all files in a folder (including files in sub-folders). I do not want to prepend a random character to any of the folder names though.
Example:
Kickdrum73.wav
Kickdrum SUB.wav
Kick808.mp3
Renamed to:
f_Kickdrum73.wav
!_Kickdrum SUB.wav
4_Kick808.mp3
If possible, I would like to be able to run this script more than once, but on subsequent runs, it just changes the randomly prepended character instead of prepending a new one.
Some of my attempts:
find ~/Desktop/test -type f -print0 | xargs -0 -n1 bash -c 'mv "$0" "a${0}"'
find ~/Desktop/test/ -type f -exec mv -v {} $(cat a {}) \;
find ~/Desktop/test/ -type f -exec echo -e "Z\n$(cat !)" > !Hat 15.wav
for file in *; do
mv -v "$file" $RANDOM_"$file"
done
Note: I am running on macOS.
Latest attempt using code from mr. fixit:
find . -type f -maxdepth 999 -not -name ".*" |
cut -c 3- - |
while read F; do
randomCharacter="${F:2:1}"
if [ $randomCharacter == '_' ]; then
new="${F:1}"
else
new="_$F"
fi
fileName="`basename $new`"
newFilename="`jot -r -c $fileName 1 A Z`"
filePath="`dirname $new`"
newFilePath="$filePath$newFilename"
mv -v "$F" "$newFilePath"
done
Here's my first answer, enhanced to do sub-directories.
Put the following in file randomize
if [[ $# != 1 || ! -d "$1" ]]; then
echo "usage: $0 <path>"
else
find $1 -type f -not -name ".*" |
while read F; do
FDIR=`dirname "$F"`
FNAME=`basename "$F"`
char2="${FNAME:1:1}"
if [ $char2 == '_' ]; then
new="${FNAME:1}"
else
new="_$FNAME"
fi
new=`jot -r -w "%c$new" 1 A Z`
echo mv "$F" "${FDIR}/${new}"
done
fi
Set the permissions with chmod a+x randomize.
Then call it with randomize your/path.
It'll echo the commands required to rename everything, so you can examine them to ensure they'll work for you. If they look right, you can remove the echo from the 3rd to last line and rerun the script.
cd ~/Desktop/test, then
find . -type f -maxdepth 1 -not -name ".*" |
cut -c 3- - |
while read F; do
char2="${F:2:1}"
if [ $char2 == '_' ]; then
new="${F:1}"
else
new="_$F"
fi
new=`jot -r -w "%c$new" 1 A Z`
mv "$F" "$new"
done
find . -type f -maxdepth 1 -not -name ".*" will get all the files in the current directory, but not the hidden files (names starting with '.')
cut -c 3- - will strip the first 2 chars from the name. find outputs paths, and the ./ gets in the way of processing prefixes.
while read VAR; do <stuff>; done is a way to deal with one line at a time
char2="${VAR:2:1} sets a variable char2 to the 2nd character of the variable VAR.
if - then - else sets new to the filename, either preceded by _ or with the previous random character stripped off.
jot -r -w "%c$new" 1 A Z tacks random 1 character from A-Z onto the beginning of new
mv old new renames the file
You can also do it all in bash and there are several ways to approach it. The first is simply creating an array of letters containing whatever letters you want to use as a prefix and then generating a random number to use to choose the element of the array, e.g.
#!/bin/bash
letters=({0..9} {A..Z} {a..z}) ## array with [0-9] [A-Z] [a-z]
for i in *; do
num=$(($RANDOM % 63)) ## generate number
## remove echo to actually move file
echo "mv \"$i\" \"${letters[num]}_$i\"" ## move file
done
Example Use/Output
Current the script outputs the changes it would make, you must remove the echo "..." surrounding the mv command and fix the escaped quotes to actually have it apply changes:
$ bash ../randprefix.sh
mv "Kick808.mp3" "4_Kick808.mp3"
mv "Kickdrum SUB.wav" "h_Kickdrum SUB.wav"
mv "Kickdrum73.wav" "l_Kickdrum73.wav"
You can also do it by generating a random number representing the ASCII character between 48 (character '0') through 126 (character '~'), excluding 'backtick'), and then converting the random number to an ASCII character and prefix the filename with it, e.g.
#!/bin/bash
for i in *; do
num=$((($RANDOM % 78) + 48)) ## generate number for '0' - '~'
letter=$(printf "\\$(printf '%03o' "$num")") ## letter from number
while [ "$letter" = '`' ]; do ## exclude '`'
num=$((($RANDOM % 78) + 48)) ## generate number
letter=$(printf "\\$(printf '%03o' "$num")")
done
## remove echo to actually move file
echo "mv \"$i\" \"${letter}_$i\"" ## move file
done
(similar output, all punctuation other than backtick is possible)
In each case you will want to place the script in your path or call it from within the directory you want to move the file in (you split split dirname and basename and join them back together to make the script callable passing the directory to search as an argument -- that is left to you)

count of files only in a list

I have a txt file that has the list of files an folders per each line. I would like to have the only count of files not folders.
Count=$(cat list.txt | wc -l)
will give me the total count.
Test the type of each file in a loop:
count=0
IFS=$'\n' # so spaces in filenames don't cause a problem
for i in $(< list.txt); do
if [ -f "$i" ]
then count=$((count + 1))
fi
done
below is very straight forward
find . -maxdepth 1 -type d -printf '%f\n' | wc -l

copy list of filenames in a textfile in bash

I need to copy a list of filenames in a textfile. Trying by this:
#!/bin/sh
mjdstart=55133
mjdend=56674
datadir=/nfs/m/ir1/ssc/evt
hz="h"
for mjd in $(seq $mjdstart $mjdend); do
find $datadir/ssc"${hz}"_allcl_mjd"${mjd}".evt -maxdepth 1 -type f -printf $datadir'/%f\n' > ssc"${hz}".list
done
I tried also:
find $datadir/ssc"${hz}"_allcl_mjd"${mjd}".evt -maxdepth 1 -type f -printf $datadir'/%f\n' | split -l999 -d - ssc"${hz}".list
Or other combinations, but clearly I am missing something: the textfile is empty. Where is my mistake?
Use >> (append) instead of > (overwrite) otherwise you will have output of last command only:
> ssc"${hz}".list
for mjd in $(seq $mjdstart $mjdend); do
find $datadir/ssc"${hz}"_allcl_mjd"${mjd}".evt -maxdepth 1 -type f -printf $datadir'/%f\n' >> ssc"${hz}".list
done
You don't need to use find here, as you simply have a range of specific file names whose existence you are checking for:
#!/bin/sh
mjdstart=55133
mjdend=56674
datadir=/nfs/m/ir1/ssc/evt
hz="h"
for mjd in $(seq $mjdstart $mjdend); do
fnname="$datadir/ssc${hz}_allcl_mjd${mjd}.evt"
[[ -f $fname ]] && printf "$fname\n"
done > "ssc$hz.list"
You are using find wrong. The first argument is the directory, in which it should search. Also, using > overwrites your list file in every turn. Use >> to concatenate:
find $datadir -maxdepth 1 -type f -name "src${hz}_allcl_mjd${mjd}.evt" >> ssc"${hz}".list

How to locate the directory where the sum of the number of lines of regular file is greatest (in bash)

Hi i'm new in Unix and bash and I'd like to ask q. how can i do this
The specified directory is given as arguments. Locate the directory
where the sum of the number of lines of regular file is greatest.
Browse all specific directories and their subdirectories. Amounts
count only for files that are directly in the directory.
I try somethnig but it's not working properly.
while [ $# -ne 0 ];
do case "$1" in
-h) show_help ;;
-*) echo "Error: Wrong arguments" 1>&2 exit 1 ;;
*) directories=("$#") break ;;
esac
shift
done
IFS='
'
amount=0
for direct in "${directories[#]}"; do
for subdirect in `find $direct -type d `; do
temp=`find "$subdirect" -type f -exec cat {} \; | wc -l | tr -s " "`
if [ $amount -lt $temp ]; then
amount=$temp
subdirect2=$subdirect
fi
done
echo Output: "'"$subdirect2$amount"'"
done
the problem is here when i use as arguments this dirc.(just example)
/home/usr/first and there are this direct.
/home/usr/first/tmp/first.txt (50 lines)
/home/usr/first/tmp/second.txt (30 lines)
/home/usr/first/tmp1/one.txt (20 lines)
it will give me on Output /home/usr/first/tmp1 100 and this is wrong it should be /home/usr/first/tmp 80
I'd like to scan all directories and all its subdirectories in depth. Also if multiple directories meets the maximum should list all.
Given your sample files, I'm going to assume you only want to look at the immediate subdirectories, not recurse down several levels:
max=-1
# the trailing slash limits the wildcard to directories only
for dir in */; do
count=0
for file in "$dir"/*; do
[[ -f "$file" ]] && (( count += $(wc -l < "$file") ))
done
if (( count > max )); then
max=$count
maxdir="$dir"
fi
done
echo "files in $maxdir have $max lines"
files in tmp/ have 80 lines
In the spirit of Unix (caugh), here's an absolutely disgusting chain of pipes that I personally hate, but it's a lot of fun to construct :):
find . -mindepth 1 -maxdepth 1 -type d -exec sh -c 'find "$1" -maxdepth 1 -type f -print0 | wc -l --files0-from=- | tail -1 | { read a _ && echo "$a $1"; }' _ {} \; | sort -nr | head -1
Of course, don't use this unless you're mentally ill, use glenn jackman's nice answer instead.
You can have great control on find's unlimited filtering possibilities, too. Yay. But use glenn's answer!

Resources