Bash create directories recursively without last - bash

How can I ensure that intermidiate components exist? Something like mkdir -p {} but without last component?

Let's say your path is $f
Just run mkdir -p `dirname $f`

Related

Make directory based on filenames before fourth underscore

I have these test files:
ABCD1234__12_Ab2_Hh_3P.mp4
ABCD1234__12_Ab2_Lw_3P.wmv
ABCD1234__12_Ab2_SSV.mov
EFGH56789__13_Mn1_SSV.avi
EFGH56789__13_Mn1_Ve1_3P.mp4
EFGH56789__13_Mn1_Ve2_3P.webm
I want to create a context service in Automator that makes directories based on the filename prefixes above like so:
ABCD1234__12_Ab2
EFGH56789__13_Mn1
...and move the files into those two accordingly. The only consistent variables in the names are underscores, so I was thinking I could delineate by those, preferably capturing the name before the fourth one.
I originally started with this very simple script:
for file in "$#"
do
mkdir "${file%.*}" && mv "$file" "${file%.*}"
done
Which makes a folder for every file and moves each file into its own folder.
I tried adding variables, various if/thens, etc. but to no avail (not a programmer by trade).
I also wrote another script to do it in a slightly different way, but with the same results to mess around with:
for folder in "$#"
do
cd "$1"
find . -type f -maxdepth 1 -exec bash -c 'mkdir -p "${0%.*}"' {} \; \
-exec bash -c 'mv "$0" "${0%.*}"' {} \;
done
I feel like there's something obvious I am missing.
Your script is splitting on dot, but you say you want to split on underscore. If the one you want to split on is the last one, the fix is trivial:
for file in "$#"
do
mkdir -p "${file%_*}" && mv "$file" "${file%_*}"
done
To get precisely the fourth, try
for file in "$#"
do
tail=${file#*_*_*_*_}
dir=${file%_"$tail"}
mkdir -p "$dir" && mv "$file" "$dir"
done
The addition of the -p option is a necessary bug fix if you want to use && here; mkdir without this option will fail if the directory already exists.
Perhaps see also the section about parameter expansions in the Bash Reference Manual which explains this syntax and its variations.
You could do something like this:
#!/bin/bash
shopt -s extglob
for file
do
if [[ $file =~ ^(([^_]*_){3}[^_]*) ]]
then
echo "${BASH_REMATCH[0]}"
else
echo "${file%.*}"
fi
done

Bash doesn't create all files and directories with loop

I'm trying to create a directory with list of directories with list of files.
Could you explain me, why this script doesn't work properly?
createapp() {
local folders=('graphql' 'migrations' 'models' 'tests')
local files=('__init__.py' 'admin.py' 'apps.py' 'views.py')
cd backend/apps
mkdir $COMMAND2
cd $COMMAND2
for folder in $folders
do mkdir $folder && cd $folder
for file in $files
do touch $file && cd ..
done
done
}
It creates a graphql directory, and an __init__.py file in it, but that's all.
There are a few problems:
You aren't iterating over the contents of the array, only the first element.
You are executing cd .. too soon; you want to do that after the loop that creates each file.
for folder in "${folders[#]}"; do
mkdir -p "$folder" &&
cd "$folder" &&
for file in "${files[#]}"; do
touch "$file"
done &&
cd ..
done
There are two ways to simplify this. If you keep the cd command, you only need one call to touch:
for folder in "${folders[#]}"; do
mkdir -p "$folder" && cd "$folder" && touch "${files[#]}" && cd ..
done
Or you can get rid of the cd command, and pass a longer path to touch:
for folder in "${folders[#]}"; do
mkdir -p "$folder" &&
for file in "${files[#]}"; do
touch "$folder/$file"
done
done
or even
for folder in "${folders[#]}"; do
mkdir -p "$folder" &&
touch "${files[#]/#/$folder/}"
done
If you want to get fancy, you can do all of this with zero loops and a combination of bash brace and array expansions:
#!/bin/bash
createapp() {
local folders=('graphql' 'migrations' 'models' 'tests')
local files=('__init__.py' 'admin.py' 'apps.py' 'views.py')
IFS=,
eval mkdir -p "{${folders[*]}}"
eval touch "{${folders[*]}}/{${files[*]}}"
}
Note that the use of eval can be dangerous, but it's pretty limited in this implementation as the two arrays are local and not using user-defined input. If this was zsh, you could embed brace and array expansion without the need for eval

How do I copy all files to a sub directory based on the file name?

I am sure this is possible with a bash script, but I want to make sure I'm not missing something obvious. Google hasn't been much help, so maybe you can be.
Assume a directory has the following files
dir/file1
dir/newfile2
dir/oldfile3
I would like to figure out the best solution for copying all files in the folder to a folder 2 levels deep based on the first two letters of the filename, so the result would be
dir/f/i/file1
dir/n/e/newfile2
dir/o/l/oldfile3
Something like this should do it:
cd dir
for file in *; do
newpath="${file:0:1}/${file:1:1}"
mkdir -p "$newpath"
cp "$file" "$newpath"
done
Be sure all your filenames are two chars or more, though.
${var:n:m} is simply Bash syntax for "substring of var starting at n of length m."
If there can also be arbitrary subdirectories, either add -r to the cp command if you want to copy recursively or add a test to ignore directories in the for loop:
cd dir
for file in *; do
if [ -f "$file" ]; then
newpath="${file:0:1}/${file:1:1}"
mkdir -p "$newpath"
cp "$file" "$newpath"
fi
done

Streamlining conditional statements (if statement) in bash

My question is more so a question about code efficiency and simplicity than it is about simply completing a task. The scenario is as such: I would like to create a bash script that uses a for loop to iterate through /Users when it is in each users home directory I want it to see if two different directories exist in the style of:
for USER in /Users/*; do
if [ -d "$USER/Library/Caches/com.spotify.Client" ]; then
rm -rf "$USER/Library/Caches/com.spotify.Client"
...but I need to check for multiple directories. How do I accomplish this in the most elegant way? I would like to avoid using a series of if statements but don't know the best way to accomplish this.
Finally, I would like to use the find command to find a file, then set the result of the find (i.e. the path to the found file) to a variable and input it into another command. Thank you.
From what I understand of your requirements, I would nest the for loops:
subdirs=(
"Library/Caches/com.spotify.client"
"some/other/subdir/"
)
for homedir in /Users/*; do
for subdir in "${subdirs[#]}"; do
dir="$homedir/$subdir"
if [[ -d "$dir" ]]; then
rm -rf "$dir"
fi
done
done
The following builds on #David Wolever's answer above; these versions are shorter and let the shell do more globbing up front.
Inline, single-loop version without use of intermediate (array) variable:
for dir in /Users/*/'Library/Caches/com.spotify.client/' /Users/*/'some/other/subdir/'; do
[[ -d "$dir" ]] && rm -rf "$dir"
done
Array-variable version:
subdirs=(
'Library/Caches/com.spotify.client/'
'some/other/subdir/'
)
for subdir in "${subdirs[#]}"; do
for dir in /Users/*/"$subdir"; do
[[ -d "$dir" ]] && rm -rf "$dir"
done
done

mkdir -p vs if [[ ! -d dirname ]]

Is there any reason to use
if [[ ! -d dirname ]]; then mkdir dirname; fi
instead of just
mkdir -p dirname
The first syntax depends on the shell you are using, not the second.
Since both fail if dirname exists not as a directory, no, there's no difference.
-d FILE True if file is a directory.
-p no error if existing, make parent directories as needed.
If dirname does not contain any parents then the two commands behave the same. However if dirname contains parents the -d will not create those. And [[ is shell-dependent.
These two ksh commands are functionally the same since both will create a directory called dirname.
mkdir -p dirname is more elegant.

Resources