Create directory and files with one command - linux

I want to make a command/function to mkdir and touch my directory and files quickly.
Terminal: cd PROJECT:
PROJECT
Terminal: quickcreate home index.js style.css .... The tree looks like:
PROJECT __ home __ index.html
\_ style.css
\_ ...
Do manually:
mkdir home
touch home/index.html
touch home/style.css
touch home/...
I want to write a command like this:
function quickcreate {
if [ $# -eq 0 ]
then
echo "No arg supplied!"
return 0
else
mkdir $1
# how can I do with S2, S3, .... to touch S1/S2 S1/S3...?
}

I recommend -p.
qc() { local p="$1";
if [[ -n "$p" ]];
then mkdir -p "$p" # can be any full or relative path;
else echo "Use: qc <dirpath> [f1[..fN]]"; return 1;
fi;
shift;
for f; do touch "$p/$f"; done;
}
$: qc
Use: qc <dirpath> [f1[..fN]]
$: cd /tmp
$: qc a/b/c 5 4 3 2 1 # relative path
$: qc a/b # no files; dir already exists; no problem
$: qc /tmp/a/b/c/d 3 2 1 # full path that partially exists
$: find a # all ok
a
a/b
a/b/c
a/b/c/1
a/b/c/2
a/b/c/3
a/b/c/4
a/b/c/5
a/b/c/d
a/b/c/d/1
a/b/c/d/2
a/b/c/d/3

You can use shift to remove positional arguments one by one.
Don't forget to double quote the directory and file names so the script works for names containing whitespace, too.
mkdir "$1"
dir=$1
shift
while (( $# )) ; do
touch "$dir/$1"
shift
done

This is another method you can use for what you want, that is using arrays:
function quickcreate {
if [ $# -eq 0 ]; then
echo "No arg supplied!"
return 0
else
dir="${#::1}"
files=(${#:1})
mkdir "$dir"
for file in "${files[#]}"; do
touch $dir/$file
done
}

Related

is there a touch that can create parent directories like mkdir -p?

I have the following two functions defined in my .zshrc
newdir(){ # make a new dir and cd into it
if [ $# != 1 ]; then
printf "\nUsage: newdir <dir> \n"
else
/bin/mkdir -p $1 && cd $1
fi
}
newfile() { # make a new file, open it for editing, here specified where
if [ -z "$1" ]; then
printf "\nUsage: newfile FILENAME \n"
printf "touches a new file in the current working directory and opens with nano to edit \n\n"
printf "Alternate usage: newfile /path/to/file FILENAME \n"
printf "touches a new file in the specified directory, creating the diretory if needed, and opens to edit with nano \n"
elif [ -n "$2" ]; then
FILENAME="$2"
DIRNAME="$1"
if [ -d "$DIRNAME" ]; then
cd $DIRNAME
else
newdir $DIRNAME
fi
else
FILENAME="$1"
fi
touch ./"$FILENAME"
nano ./"$FILENAME"
}
but I am wondering, is there a version of touch that acts similar to mkdir -p, in that it can create parent dirs as needed in one line/command?
There is no touch that can create parent directory path, so write your own in standard POSIX-shell grammar that also works with zsh:
#!/usr/bin/env sh
touchp() {
for arg
do
# Get base directory
baseDir=${arg%/*}
# If whole path is not equal to the baseDire (sole element)
# AND baseDir is not a directory (or does not exist)
if ! { [ "$arg" = "$baseDir" ] || [ -d "$baseDir" ];}; then
# Creates leading directories
mkdir -p "${arg%/*}"
fi
# Touch file in-place without cd into dir
touch "$arg"
done
}
With zsh you can do:
mkdir -p -- $#:h && : >>| $#
mkdir is given the "head" of each argument to make the directories (man zshexpn says the :h expansion modifier works like the dirname tool). Then, assuming you have not unset the MUTLIOS option, the output of : (a command that produces no output) is appended to the files.

Prefixing sub-folder names to a filename, and moving file to current directory

I have a car stereo that reads USB sticks, but the stereo's "random" function only works on one folder, so I want to move all files to a parent folder, "STICK/music", but to have the Artist, and Album if any, added to the filename.
But what I've done is sort of hard-coded, and I'd like to be able to use different sticks.
I would like to have these ("RHYTHMBLUES" is the name of the stick):
/media/homer/RHYTHMBLUES/music/Artist/Album/name1.mp3
/media/homer/RHYTHMBLUES/music/Artist/Album/name2.mp3
/media/homer/RHYTHMBLUES/music/Artist/name3.mp3
/media/homer/RHYTHMBLUES/music/Artist/name4.mp3
renamed with folder names and moved to "music":
/media/homer/RHYTHMBLUES/music/Artist-Album-name1.mp3
/media/homer/RHYTHMBLUES/music/Artist-Album-name2.mp3
/media/homer/RHYTHMBLUES/music/Artist-name3.mp3
/media/homer/RHYTHMBLUES/music/Artist-name4.mp3
I thought of having a bash command to call "doit":
find . -type d -exec sh -c "cd \"{}\" ; /home/homer/doit \"{}\" " \;
and "doit" would contain:
#!/bin/sh -x
echo --------------------------------
pwd
for i in *.mp3
do new=$(printf "${1}/${i}")
# remove the ./ at the beginning of the name
new=`echo "${new}" | cut -c 3-`
# replace folder separators
new=`echo "${new}" | tr '/' '-'`
echo "$i" "/media/homer/RHYTHMBLUES/music/$new"
mv -T "$i" "/media/homer/RHYTHMBLUES/music/$new"
done
doitbaby.sh
#!/bin/sh
moveTo () { # $1=directory, $2=path, $3=targetName
local path="$(realpath "$1")"
for file in "$path"/*; do
if [ -d "$file" -a -n "$3" ]; then
moveTo "$file" "$2" "$3""${file##*/}"-
elif [ -d "$file" ]; then
moveTo "$file" "$path" "${file##*/}"-
elif [ -f "$file" -a "${file##*.}" = 'mp3' ]; then
echo 'FROM: '"$file"
echo 'TO : '"$2"/"$3""${file##*/}"
# mv "$file" "$2"/"$3""${file##*/}" # Uncomment this for real usage.
fi
done
}
removeEmptyDirs () {
local path="$(realpath "$1")"
for file in "$path"/*; do
if [ -d "$file" ]; then
rm -fR "$file"
fi
done
}
moveTo "$1"
# removeEmptyDirs "$1" # Uncomment this for real usage.
Note
I have commented 2 lines in the above script. Test it first before any further real actions.
Call the script like this: (Make sure the path is correct. This is the expected path by this script by the way.)
./doitbaby.sh '/media/homer/RHYTHMBLUES/music/'

Recursion in BASH

My task is create script which lists all subdirs in user's directory and write them in a file.
I created a recursive function which should run through all directories from main and write the names of their subdirs in the file. But the script runs for first folders in my home dirs until reaching the folder without the subfolder. How do I do it correctly?
#!/bin/bash
touch "/home/neo/Desktop/exercise1/backup.txt"
writeFile="/home/neo/Desktop/exercise1/backup.txt"
baseDir="/home/neo"
print(){
echo $1
cd $1
echo "============">>$writeFile
pwd>>$writeFile
echo "============">>$writeFile
ls>>$writeFile
for f in $("ls -R")
do
if [ -d "$f" ]
then
print $1"/"$f
fi
done
}
print $baseDir
to get all folders within a path you can simply do:
find /home/neo -type d > /home/neo/Desktop/exercise1/backup.txt
done
Try this
fun(){
[[ -d $1 ]] && cd $1
echo $PWD
d=$(echo *)
[[ -d $d ]] && cd $d || return
fun
}

How to rename all files in a directory adding a prefix

I first create 10 digit random number
export mask=$RANDOM$RANDOM$RANDOM; let "mask %= 10000000000";
This command works well
for i in /home/testing/*; do mv "$i" "$mask$i"; done
The problem with the above command is that it only works when I am in /home/testing. As soon as I move the script, i get this error
mv: cannot move ‘/home/testing/rec001.mp4’ to ‘3960731225/home/testing/rec001.mp4’: No such file or directory
What am I doing wrong here?
You need to separate the path from the filename before you apply the mask. For example, to use in a script where the directory is passed as an argument to the script:
path="$1"
## Note: this assumes you are exporting mask earlier. If not, set mask here
for i in "${path}"/*; do
dir="${i%/*}" # path component
ffname="${i##*/}" # filename component
mv "$i" "${dir}/${mask}${ffname}"
done
This will apply mask to all files in the given directory, no matter where the directory is.
An example of a script that incorporates this is shown below. You can save this script wherever you like. You can either make it executable chmod 0755 scriptname or call it with bash scriptname. To use the script, add the path you want to prefix the files in as the first argument. E.g bash scriptname /path/to/files (or just scriptname /path/to/files if you made it executable):
#!/bin/bash
# validate input
[ -n "$1" ] || {
printf "error: insufficient input. Usage: %s /path/to/files\n" "${0//\//}"
exit 1
}
# validate directory
[ -d "$1" ] || {
printf "error: directory not found: '%s'\n" "$1"
exit 1
}
path="$1"
## Note: this assumes you are exporting mask earlier. If not, set mask here
## validate mask set and is 10 chars (added per comment)
[ -n "$mask" -a "${#mask}" -eq 10 ] || {
printf "error: mask '%s' either unset or not 10 characters\n" "$mask"
exit 1
}
# move files
for i in "${path}"/*; do
[ -f "$i" ] || continue # if not file, skip
dir="${i%/*}" # path component
ffname="${i##*/}" # full filename component (with .ext)
mv "$i" "${dir}/${mask}${ffname}"
done
Here is a sample of what moves would take place with the script named prefix.sh when called on the directory dat in the current working directory and when called on ~/tmp outside the current directory:
output (mask=3960731225):
$ ./prefix.sh dat
dat/f1f2.dat => dat/3960731225f1f2.dat
dat/field.txt => dat/3960731225field.txt
dat/flop.txt => dat/3960731225flop.txt
dat/hh.dat => dat/3960731225hh.dat
dat/hh1.dat => dat/3960731225hh1.dat
dat/hostnm => dat/3960731225hostnm
dat/hosts.dat => dat/3960731225hosts.dat
$ ./prefix.sh ~/tmp
/home/david/tmp/tcpd.tar.xz => /home/david/tmp/3960731225tcpd.tar.xz
/home/david/tmp/tcpdump-capt => /home/david/tmp/3960731225tcpdump-capt
/home/david/tmp/tcpdump.new.1000 => /home/david/tmp/3960731225tcpdump.new.1000
/home/david/tmp/test => /home/david/tmp/3960731225test
There is two commands that is quite helpful, basename and dirname.
They will give you the dir part and the filename, have a look at this test script.
#!/bin/bash
mask=$RANDOM$RANDOM$RANDOM; let "mask %= 10000000000";
echo $mask
mkdir -p testing
> testing/nisse.txt
> testing/guste.txt
> testing/berra.txt
ls testing/
for i in testing/*
do
file=$(basename $i)
dir=$(dirname $i)
newfile=$mask$file
echo $i $dir $file $newfile
mv "$dir/$file" "$dir/$newfile"
done
ls testing/
And it will output:
247639260
berra.txt gusten.txt nisse.txt
testing/berra.txt testing berra.txt 247639260berra.txt
testing/guste.txt testing guste.txt 247639260guste.txt
testing/nisse.txt testing nisse.txt 247639260nisse.txt
247639260berra.txt 247639260guste.txt 247639260nisse.txt
Please note that I wrote it very verbose to make it more clear and readable.
How about adding a cd command before your command now, wherever you want to move the script it works,
cd /home/testing
for i in /home/testing/*; do mv "$i" "$mask$i"; done

Bash alias utilizing the command inputs

I want to create a bash alias to do the following:
Assume I am at the following path:
/dir1/dir2/dir3/...../dirN
I want to go up to dir3 directly without using cd ... I will just write cdd dir3 and it should go directly to /dir1/dir2/dir3. cdd is my alias name.
I wrote the following alias, but it doesn't work:
alias cdd='export newDir=$1; export myPath=`pwd | sed "s/\/$newDir\/.*/\/$newDir/"`; cd $myPath'
Simply it should get the current full path, then remove anything after the new destination directory, then cd to this new path
The problem with my command is that $1 doesn't get my input to the command cdd
This is a slightly simpler function that I think achieves what you're trying to do:
cdd() { cd ${PWD/$1*}$1; }
Explanation:
${PWD/$1*}$1 takes the current working directory and strips off everything after the string passed to it (the target directory), then adds that string back. This is then used as an argument for cd. I didn't bother adding any error handling as cdwill take care of that itself.
Example:
[atticus:pgl]:~/tmp/a/b/c/d/e/f $ cdd b
[atticus:pgl]:~/tmp/a/b $
It's a little ugly, but it works.
Here's a function - which you could place in your shell profile - which does what you want; note that in addition to directory names it also supports levels (e.g., cdd 2 to go up 2 levels in the hierarchy); just using cdd will move up to the parent directory.
Also note that matching is case-INsensitive.
The code is taken from "How can I replace a command line argument with tab completion?", where you'll also find a way to add complementary tab-completion for ancestral directory names.
cdd ()
{
local dir='../';
[[ "$1" == '-h' || "$1" == '--help' ]] && {
echo -e "usage:
$FUNCNAME [n]
$FUNCNAME dirname
Moves up N levels in the path to the current working directory, 1 by default.
If DIRNAME is given, it must be the full name of an ancestral directory (case does not matter).
If there are multiple matches, the one *lowest* in the hierarchy is changed to." && return 0
};
if [[ -n "$1" ]]; then
if [[ $1 =~ ^[0-9]+$ ]]; then
local strpath=$( printf "%${1}s" );
dir=${strpath// /$dir};
else
if [[ $1 =~ ^/ ]]; then
dir=$1;
else
local wdLower=$(echo -n "$PWD" | tr '[:upper:]' '[:lower:]');
local tokenLower=$(echo -n "$1" | tr '[:upper:]' '[:lower:]');
local newParentDirLower=${wdLower%/$tokenLower/*};
[[ "$newParentDirLower" == "$wdLower" ]] && {
echo "$FUNCNAME: No ancestral directory named '$1' found." 1>&2;
return 1
};
local targetDirPathLength=$(( ${#newParentDirLower} + 1 + ${#tokenLower} ));
dir=${PWD:0:$targetDirPathLength};
fi;
fi;
fi;
pushd "$dir" > /dev/null
}
I agree with mklement0, this should be a function. But a simpler one.
Add this to your .bashrc:
cdd () {
newDir="${PWD%%$1*}$1"
if [ ! -d "$newDir" ]; then
echo "cdd: $1: No such file or directory" >&2
return 1
fi
cd "${newDir}"
}
Note that if $1 (your search string) appears more than once in the path, this function will prefer the first one. Note also that if $1 is a substring of a path, it will not be found. For example:
[ghoti#pc ~]$ mkdir -p /tmp/foo/bar/baz/foo/one
[ghoti#pc ~]$ cd /tmp/foo/bar/baz/foo/one
[ghoti#pc /tmp/foo/bar/baz/foo/one]$ cdd foo
[ghoti#pc /tmp/foo]$ cd -
/tmp/foo/bar/baz/foo/one
[ghoti#pc /tmp/foo/bar/baz/foo/one]$ cdd fo
cdd: fo: No such file or directory
If you'd like to include the functionality of going up 2 levels by running cdd 2, this might work:
cdd () {
newDir="${PWD%%$1*}$1"
if [ "$1" -gt 0 -a "$1" = "${1%%.*}" -a ! -d "$1" ]; then
newDir=""
for _ in $(seq 1 $1); do
newDir="../${newDir}"
done
cd $newDir
return 0
elif [ ! -d "$newDir" ]; then
echo "cdd: $1: No such file or directory" >&2
return 1
fi
cd "${newDir}"
}
The long if statement verifies that you've supplied an integer that is not itself a directory. We build a new $newDir so that you can cd - to get back to your original location if you want.

Resources