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

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.

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 create directories recursively without last

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`

Comparing relative paths in bash script

I am trying to build a bash script capable of comparing two directories given as arguments $1 and $2, and changing the files' timestamps from the second directory ( if they are not different than a given timestamp $3 ) to be the same as the files with the same name in the first directory. I'm doing okay with that, but I don't see how to access the folders inside the given directories, and compare the files inside those folders.
For example, if I have Directory1 and Directory2 given as arguments:
Directory1 contains:
-text.txt
-folder1/secondfile.txt
-folder2/thirdfile.txt
and Directory2 contains:
-text.txt
-folder1/secondfile.txt
-folder3/thirdfile.txt
so in this case I want my script to modify the files text.txt and secondfile.txt, but not the thirdfile.txt because the relative paths are not the same. How would my script access folders in the directory and how would it compare relative paths? I have managed to do what I wanted with files from the directory, but not folders, and I have no idea how to compare relative paths, even though I searched around I couldn't find it.
So far I've done this script (with help from SO):
#!/bin/bash
cd "$1"
function check {
for i in *; do
if [[-d "$i"]]; then
cd "$i"
check
cd -
fi
if [[-f "$i"]]; then
if [[stat %y "$i" == "$3"]]; then
#if [[path check]];then
#touch -r "$i" "$2/path/$i"
fi
}
and I don't know how to do the [[path check]] which should check if both files have the same relative path (relative to the directories given as arguments).
EDIT:
As the answer suggests, is the following code the right way to do it?
#!/bin/bash
cd "$1"
shopt -s globstar
for i in **; do
if [[stat %y "$i" == "$3"]]; then
if [["$1/$i" == "$2/$i"]];then
touch -r "$i" "$2/$i"
fi
There was an answer here before, which I wanted to reply to, suggesting using shopt -s globstar and ** instead of *.
The additional question was something along the lines of "Would I be able to compare the relative paths?".
Yes, you would. With shopt -s globstar, ** expands to include the relative path to each file. So it would return text.txt folder1/secondfile.txt folder2/thirdfile.txt.
EDIT:
You should not need to cd "$1" either, for "$1" and "$2" would not exist inside dir "$1". Try something along the lines of:
#!/usr/bin/env bash
shopt -s globstar
for i in $(cd "$1"; echo **); do
if [[ $(stat -c %y "$1/$i") == "$3" ]]; then
if [[ -f "$1/$i" ]] && [[ -f "$2/$i" ]]; then
touch -r "$1/$i" "$2/$i"
fi
fi
done

Loop through directories, check if string exists in one file, move another file

I'm working on a bash script that should do the following: for every directory beginning with Event_*, (in cat eventList), cd into the directory, and if the string "ZJ.ROT" exists in the file *.mcp, I want to copy the file "ROT" to another directory. In simpler terms: loop through directories: if string "ZJ.ROT" exists in a file in that directory, output another file from that directory to a separate directory.
#!/bin/bash
mkdir newdire
for dir in `cat eventList`; do
cd $dir
pwd
if grep "ZJ.KNYN" *.mcp; then
cp "ROT" "newdire"
fi
done
The error I get is:
./azim.sh: line 5: cd: Event_2014.11.21.10.10.19.630: No such file or directory
/Users/files/Event_2013.12.01.06.29.57.800
grep: *.mcp: No such file or directory
For some reason, this for loop isn't looping through each directory, but it's stuck in the first directory Event_2013.... Any ideas about how to implement this code?
After the first time you cd to a subdirectory you are in it for all future loop iterations so your subsequent cds will fail, as you are experiencing. You also need to quote your variables and there's other issues. Try this:
pwd="$PWD"
mkdir newdire
while IFS= read -r dir; do
cd "$dir"
grep -Fq "ZJ.KNYN" *.mcp &&
cp "ROT" "${pwd}/newdire"
cd "$pwd"
done < eventList
but of course you don't actually need to cd:
mkdir newdire
while IFS= read -r dir; do
grep -Fq "ZJ.KNYN" "$dir"/*.mcp &&
cp "${dir}/ROT" newdire
done < eventList
Problem seems to be here:
if grep "ZJ.KNYN" *.mcp; then
You should use -q option in grep to suppress the output and check the return status like this:
if grep -qF "ZJ.KNYN" *.mcp; then
-F is for fixed string search.
Also there is no need to change directory inside the loop.
Your full script can be better rewritten as:
#!/bin/bash
mkdir newdire
for dir in Event_*; do
if [[ -d "$dir" ]] && grep -qF "ZJ.KNYN" "$dir"/*.mcp 2>/dev/null; then
cp "$dir/ROT" "newdire/"
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

Resources