For a while, I've had a need for a bash script to make a directory and cd into it. Most of the solutions online work but are very minimal so I wanted to make one that handles things like creating parent directories and permission checking. Here's my code:
#!/bin/bash
function mkcd() {
# Check for no arguments
if "$#" -eq 0; then
echo "Error: no arguments provided"
return 1
fi
# Checks if help flag is used
# Not with other flags to ensure the directory isn't assumed to be a flag
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "mkcd - Makes a directory and changes directory to it\n"
echo "Flags:"
echo " -h, --help Display help message"
echo " -p, --parents Makes parent directories as neeeded"
echo " -a, --absolute Receive an absolute directory instead of relative\n"
echo "Format: mkcd [arguments] directory"
return 0
fi
# Flag checker
while test "$#" -gt 1; do
case "$1" in
-p | --parents)
mkcd_parents=true
shift
;;
-a | --absolute)
shift
;;
esac
done
mkcd_path="$1"
if [[ ! -w "$PWD" ]]; then
echo "Error: Permission denied"
return 1
fi
if [[ -d "$mkcd_path" ]]; then
echo "Error: Directory already exists"
return 1
fi
if "$mkcd_parents"; then
mkdir -p "$mkcd_path"
cd "$mkcd_path"
else
mkdir "$mkcd_path"
cd "$mkcd_path"
fi
}
I also sourced it in my .zshrc file with source ~/bin/*
When I run the command, I get this output:
~ ❯ mkcd test_dir
mkcd:3: command not found: 1
mkcd:45: permission denied:
~/test_dir ❯
Does anyone understand why I'm getting this error?
if "$#" -eq 0; then
Since you have one argument to the script, that becomes after expansions
if 1 -eq 0; then
You probably meant to do
if [[ "$#" -eq 0 ]]; then
instead. (With either of [ .. ] or [[ .. ]].)
As an aside, I would change this
if "$mkcd_parents"; then
to
if [ "$mkcd_parents" = "true" ]; then
Otherwise if the -p option isn't given, $mkcd_parents is unset, "$mkcd_parents" expands to the empty string, and you get an error about that command not being found.
#!/bin/bash
#Toggle Script
# $dirserver/A -> $dirproject/{trunk|branches}/A
if [[ "$1" == "dw" || -z "$1" ]]; then
echo "[+] Delete old link ( $dirserver/A )... "
rm "$dirserver/A"
if [[ "$(readlink -f $dirserver/A)" == *"branches"* ]]; then
ln -s "$dirproject/trunk/A" "$dirserver/A"
echo "[+] Done. You are now in TRUNK"
else
ln -s "$dirproject/branches/A" "$dirserver/A"
echo "[+] Done. You are now in BRANCH."
fi
fi
Expected functionality: Do toggle between symlinks, BRANCH or TRUNK .
Error: ./toggle.sh dw Always end in BRANCH.
Notes: No. There is no word "branches" when it points to trunk.
Thank you in advance.
You are deleting the old link before checking it. Just do not delete it at all, only overwrite.
if [[ "$1" == "dw" || -z "$1" ]]; then
if [[ "$(readlink -f "$dirserver/A")" == *branches* ]]; then
ln -f -s "$dirproject/trunk/A" "$dirserver/A"
else
ln -f -s "$dirproject/branches/A" "$dirserver/A"
fi
fi
I have a bash script which is intended to be idempotent. It creates symlinks, and it should be okay if the links are already there.
Here's an extract
L="/var/me/foo"
if [[ -e "$L" ]] && ! [[ -L "$L" ]];
then
echo "$L exists but is not a link."
exit 1;
elif [[ -e "$L" ]] && [[ -L "$L" ]];
then
echo "$L exists and is a link."
else
ln -s "/other/place" "$L" ||
{
echo "Could not chown ln -s for $L";
exit 1;
}
fi
The file /var/me/foo is already a symlink pointing to /other/place, according to ls -l.
Nevertheless, when I run this script the if and elif branches are not entered, instead we go into the else and attempt the ln, which fails because the file already exists.
Why do my tests not work?
Because you only check [ -L "$L" ] if [ -e "$L" ] is true, and [ -e "$L" ] returns false for a link pointing to a destination that doesn't exist, you don't detect links that point to locations that don't exist.
The below logic is a bit more comprehensive.
link=/var/me/foo
dest=/other/place
# because [[ ]] is in use, quotes are not mandatory
if [[ -L $link ]]; then
echo "$link exists as a link, though its target may or may not exist" >&2
elif [[ -e $link ]]; then
echo "$link exists but is not a link" >&2
exit 1
else
ln -s "$dest" "$link" || { echo "yadda yadda" >&2; exit 1; }
fi
I'm seeing this every time I open terminal, after a RVM installation:
Last login: Tue Dec 2 10:35:44 on ttys000
rvm_debug () {
(( ${rvm_debug_flag:-0} )) || return 0
if rvm_pretty_print stderr
then
printf "%b" "${rvm_debug_clr:-}$*${rvm_reset_clr:-}\n"
else
printf "%b" "$*\n"
fi >&2
}
This is my bash_profile:
[10:54:13] old_ian :: Ians-MacBook-Pro-2 ➜ ~ ⭑ cat .bash_profile
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
export SSL_CERT_FILE=/usr/local/etc/cacert.pem
export PATH=/usr/local/sbin:$PATH
### Added by the Heroku Toolbelt
export PATH="/usr/local/heroku/bin:$PATH"
### RVM
source $HOME/.rvm/scripts/rvm
PATH="/Applications/Postgres.app/Contents/MacOS/bin:$PATH"
alias bex="bundle exec"
alias grep="grep --color=auto"
alias vi=vim
alias postgres="pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log"
[10:54:19] old_ian :: Ians-MacBook-Pro-2 ➜ ~ ⭑
Maybe something went wrong with it's install ?
I've been using it with no problems.
UPDATES:
Posting cat RVM
#!/usr/bin/env bash
# rvm : Ruby enVironment Manager
# https://rvm.io
# https://github.com/wayneeseguin/rvm
# partial duplication marker dkjnkjvnckbjncvbkjnvkj
# prevent from loading in sh shells
if
\command \test -n "${BASH_VERSION:-}" -o -n "${ZSH_VERSION:-}"
then
case "`uname`" in
(CYGWIN*) __shell_name="`\command \ps -p $$ | \command \awk 'END {print $NF}'` 2>/dev/null" ;;
(*) __shell_name="`\command \ps -p $$ -o comm=`" ;;
esac
case "$__shell_name" in
(""|dash|sh|*/dash|*/sh) return 0 ;; # silently stop in sh shells
esac
unset __shell_name
else
return 0
fi
# also duplicated in scripts/base
__rvm_has_opt()
{
{
# pre-gnu
[[ -n "${ZSH_VERSION}" ]] && setopt | GREP_OPTIONS="" \command \grep "^${1}$" >/dev/null 2>&1
} ||
{
[[ -n "${BASH_VERSION}" ]] && [[ ":$SHELLOPTS:" =~ ":${1}:" ]]
} ||
return 1
}
# Do not allow sourcing RVM in `sh` - it's not supported
# return 0 to exit from sourcing this script without breaking sh
if __rvm_has_opt "posix"
then return 0
fi
# TODO: Alter the variable names to make sense
\export HOME rvm_prefix rvm_user_install_flag rvm_path
HOME="${HOME%%+(\/)}" # Remove trailing slashes if they exist on HOME
[[ -n "${rvm_stored_umask:-}" ]] || export rvm_stored_umask=$(umask)
if (( ${rvm_ignore_rvmrc:=0} == 0 ))
then
rvm_rvmrc_files=("/etc/rvmrc" "$HOME/.rvmrc")
if [[ -n "${rvm_prefix:-}" ]] && ! [[ "$HOME/.rvmrc" -ef "${rvm_prefix}/.rvmrc" ]]
then rvm_rvmrc_files+=( "${rvm_prefix}/.rvmrc" )
fi
for rvmrc in "${rvm_rvmrc_files[#]}"
do
if [[ -f "$rvmrc" ]]
then
# pre-gnu
if GREP_OPTIONS="" \command \grep '^\s*rvm .*$' "$rvmrc" >/dev/null 2>&1
then
printf "%b" "
Error:
$rvmrc is for rvm settings only.
rvm CLI may NOT be called from within $rvmrc.
Skipping the loading of $rvmrc"
return 1
else
source "$rvmrc"
fi
fi
done
unset rvm_rvmrc_files
fi
# duplication marker jdgkjnfnkjdngjkfnd4fd
# detect rvm_path if not set
if [[ -z "${rvm_path:-}" ]]
then
if [[ -n "${BASH_SOURCE:-$_}" && -f "${BASH_SOURCE:-$_}" ]]
then
rvm_path="${BASH_SOURCE:-$_}"
rvm_path="$( \command \cd "${rvm_path%/scripts/rvm}">/dev/null; pwd )"
rvm_prefix=$( dirname $rvm_path )
elif (( UID == 0 ))
then
if (( ${rvm_user_install_flag:-0} == 0 ))
then
rvm_prefix="/usr/local"
rvm_path="${rvm_prefix}/rvm"
else
rvm_prefix="$HOME"
rvm_path="${rvm_prefix}/.rvm"
fi
else
if [[ -d "$HOME/.rvm" && -s "$HOME/.rvm/scripts/rvm" ]]
then
rvm_prefix="$HOME"
rvm_path="${rvm_prefix}/.rvm"
else
rvm_prefix="/usr/local"
rvm_path="${rvm_prefix}/rvm"
fi
fi
else
# remove trailing slashes, btw. %%/ <- does not work as expected
rvm_path="${rvm_path%%+(\/)}"
fi
# guess rvm_prefix if not set
if [[ -z "${rvm_prefix}" ]]
then
rvm_prefix=$( dirname $rvm_path )
fi
# duplication marker kkdfkgnjfndgjkndfjkgnkfjdgn
case "$rvm_path" in
(/usr/local/rvm) rvm_user_install_flag=0 ;;
($HOME/*|/${USER// /_}*) rvm_user_install_flag=1 ;;
(*) rvm_user_install_flag=0 ;;
esac
export rvm_loaded_flag
if [[ -n "${BASH_VERSION:-}" || -n "${ZSH_VERSION:-}" ]] &&
typeset -f rvm >/dev/null 2>&1
then
rvm_loaded_flag=1
else
rvm_loaded_flag=0
fi
if
(( ${rvm_loaded_flag:=0} == 0 )) || (( ${rvm_reload_flag:=0} == 1 ))
then
if
[[ -n "${rvm_path}" && -d "$rvm_path" ]]
then
true ${rvm_scripts_path:="$rvm_path/scripts"}
if
[[ ! -f "$rvm_scripts_path/base" ]]
then
printf "%b" "WARNING:
Could not source '$rvm_scripts_path/base' as file does not exist.
RVM will likely not work as expected.\n"
elif
! source "$rvm_scripts_path/base"
then
printf "%b" "WARNING:
Errors sourcing '$rvm_scripts_path/base'.
RVM will likely not work as expected.\n"
else
__rvm_ensure_is_a_function
__rvm_setup
export rvm_version
rvm_version="$(\command \cat "$rvm_path/VERSION") ($(\command \cat "$rvm_path/RELEASE" 2>/dev/null))"
alias rvm-restart="rvm_reload_flag=1 source '${rvm_scripts_path:-${rvm_path}/scripts}/rvm'"
# Try to load RVM ruby if none loaded yet
__path_to_ruby="$( builtin command -v ruby 2>/dev/null || true )"
if
[[ -z "${__path_to_ruby}" ]] ||
[[ "${__path_to_ruby}" != "${rvm_path}"* ]] ||
[[ "${__path_to_ruby}" == "${rvm_path}/bin/ruby" ]]
then
if [[ -s "$rvm_path/environments/default" ]]
then source "$rvm_path/environments/default"
fi
if
[[ ${rvm_project_rvmrc:-1} -gt 0 ]] &&
! __function_on_stack __rvm_project_rvmrc
then
# Reload the rvmrc, use promptless ensuring shell processes does not
# prompt if .rvmrc trust value is not stored, revert to default on fail
if
rvm_current_rvmrc=""
rvm_project_rvmrc_default=2 rvm_promptless=1 __rvm_project_rvmrc
then
rvm_hook=after_cd
source "${rvm_scripts_path:-${rvm_path}/scripts}/hook"
fi
fi
fi
unset __path_to_ruby
# Makes sure rvm_bin_path is in PATH atleast once.
[[ ":${PATH}:" == *":${rvm_bin_path}:"* ]] || PATH="$PATH:${rvm_bin_path}"
if
(( ${rvm_reload_flag:=0} == 1 ))
then
[[ "${rvm_auto_reload_flag:-0}" == 2 ]] || printf "%b" 'RVM reloaded!\n'
# make sure we clean env on reload
__rvm_env_loaded=1
unset __rvm_project_rvmrc_lock
fi
rvm_loaded_flag=1
__rvm_teardown
fi
else
printf "%b" "\n\$rvm_path ($rvm_path) does not exist."
fi
unset rvm_prefix_needs_trailing_slash rvm_gems_cache_path rvm_gems_path rvm_project_rvmrc_default rvm_gemset_separator rvm_reload_flag
fi
Hope this Helps!
I'm trying to write an extremely simple script in Ubuntu which would allow me to pass it either a filename or a directory, and be able to do something specific when it's a file, and something else when it's a directory. The problem I'm having is when the directory name, or probably files too, has spaces or other escapable characters are in the name.
Here's my basic code down below, and a couple tests.
#!/bin/bash
PASSED=$1
if [ -d "${PASSED}" ] ; then
echo "$PASSED is a directory";
else
if [ -f "${PASSED}" ]; then
echo "${PASSED} is a file";
else
echo "${PASSED} is not valid";
exit 1
fi
fi
And here's the output:
andy#server~ $ ./scripts/testmove.sh /home/andy/
/home/andy/ is a directory
andy#server~ $ ./scripts/testmove.sh /home/andy/blah.txt
/home/andy/blah.txt is a file
andy#server~ $ ./scripts/testmove.sh /home/andy/blah\ with\ a\ space.txt
/home/andy/blah with a space.txt is not valid
andy#server~ $ ./scripts/testmove.sh /home/andy\ with\ a\ space/
/home/andy with a space/ is not valid
All of those paths are valid, and exist.
That should work. I am not sure why it's failing. You're quoting your variables properly. What happens if you use this script with double [[ ]]?
if [[ -d $PASSED ]]; then
echo "$PASSED is a directory"
elif [[ -f $PASSED ]]; then
echo "$PASSED is a file"
else
echo "$PASSED is not valid"
exit 1
fi
Double square brackets is a bash extension to [ ]. It doesn't require variables to be quoted, not even if they contain spaces.
Also worth trying: -e to test if a path exists without testing what type of file it is.
At least write the code without the bushy tree:
#!/bin/bash
PASSED=$1
if [ -d "${PASSED}" ]
then echo "${PASSED} is a directory";
elif [ -f "${PASSED}" ]
then echo "${PASSED} is a file";
else echo "${PASSED} is not valid";
exit 1
fi
When I put that into a file "xx.sh" and create a file "xx sh", and run it, I get:
$ cp /dev/null "xx sh"
$ for file in . xx*; do sh "$file"; done
. is a directory
xx sh is a file
xx.sh is a file
$
Given that you are having problems, you should debug the script by adding:
ls -ld "${PASSED}"
This will show you what ls thinks about the names you pass the script.
Using -f and -d switches on /bin/test:
F_NAME="${1}"
if test -f "${F_NAME}"
then
echo "${F_NAME} is a file"
elif test -d "${F_NAME}"
then
echo "${F_NAME} is a directory"
else
echo "${F_NAME} is not valid"
fi
Using the "file" command may be useful for this:
#!/bin/bash
check_file(){
if [ -z "${1}" ] ;then
echo "Please input something"
return;
fi
f="${1}"
result="$(file $f)"
if [[ $result == *"cannot open"* ]] ;then
echo "NO FILE FOUND ($result) ";
elif [[ $result == *"directory"* ]] ;then
echo "DIRECTORY FOUND ($result) ";
else
echo "FILE FOUND ($result) ";
fi
}
check_file "${1}"
Output examples :
$ ./f.bash login
DIRECTORY FOUND (login: directory)
$ ./f.bash ldasdas
NO FILE FOUND (ldasdas: cannot open `ldasdas' (No such file or directory))
$ ./f.bash evil.php
FILE FOUND (evil.php: PHP script, ASCII text)
FYI: the answers above work but you can use -s to help in weird situations by checking for a valid file first:
#!/bin/bash
check_file(){
local file="${1}"
[[ -s "${file}" ]] || { echo "is not valid"; return; }
[[ -d "${file}" ]] && { echo "is a directory"; return; }
[[ -f "${file}" ]] && { echo "is a file"; return; }
}
check_file ${1}
Using stat
function delete_dir () {
type="$(stat --printf=%F "$1")"
if [ $? -ne 0 ]; then
echo "$1 directory does not exist. Nothing to delete."
elif [ "$type" == "regular file" ]; then
echo "$1 is a file, not a directory."
exit 1
elif [ "$type" == "directory" ]; then
echo "Deleting $1 directory."
rm -r "$1"
fi
}
function delete_file () {
type="$(stat --printf=%F "$1")"
if [ $? -ne 0 ]; then
echo "$1 file does not exist. Nothing to delete."
elif [ "$type" == "directory" ]; then
echo "$1 is a regular file, not a directory."
exit 1
elif [ "$type" == "regular file" ]; then
echo "Deleting $1 regular file."
rm "$1"
fi
}
https://linux.die.net/man/2/stat
https://en.m.wikipedia.org/wiki/Unix_file_types
A more elegant solution
echo "Enter the file name"
read x
if [ -f $x ]
then
echo "This is a regular file"
else
echo "This is a directory"
fi
Answer based on the title:
Check if passed argument is file or directory in Bash
This works also if the provided argument has a trailing slash .e.g. dirname/
die() { echo $* 1>&2; exit 1; }
# This is to remove the the slash at the end: dirName/ -> dirName
fileOrDir=$(basename "$1")
( [ -d "$fileOrDir" ] || [ -f "$fileOrDir" ] ) && die "file or directory $fileOrDir already exists"
Testing:
mkdir mydir
touch myfile
command dirName
# file or directory mydir already exists
command dirName/
# file or directory mydir already exists
command filename
# file or directory myfile already exists
#!/bin/bash
echo "Please Enter a file name :"
read filename
if test -f $filename
then
echo "this is a file"
else
echo "this is not a file"
fi
One liner
touch bob; test -d bob && echo 'dir' || (test -f bob && echo 'file')
result is true (0)(dir) or true (0)(file) or false (1)(neither)
This should work:
#!/bin/bash
echo "Enter your Path:"
read a
if [[ -d $a ]]; then
echo "$a is a Dir"
elif [[ -f $a ]]; then
echo "$a is the File"
else
echo "Invalid path"
fi