How to setup prompt with three nearest folders ../c/Users/test - bash

Here's a sample: ../c/Users/test
I stil have no idea how to achieve this zsh's prompt, if possible also how it will be in bash?
EDIT: the nearest means my pwd will show for i.e: /home/abc/c/Users/test.
If I'm under /home/abc/c or /home/abc or /home, prompt should be /home/abc/c> or /home/abc> or /home>
So only current path that excesses three folders will have .. appended in front and the three nearest folders.

ZSH
In zsh this can be achieved entirely with built-in features. You just have to place
%(4/|../%3d|%d)
in your PROMPT parameter (also known as PS1).
For example:
PROMPT='[%m#%n %(4/|../%3d|%d)]%# '
Would get you something like
[abc#machine ../c/Users/test]%
when the current directory is /home/abc/c/Users/test.
Explanation:
%(x|true-text|false-text): x represents a test, if it evaluates to true it zsh will print what is placed as true-text else it will print the false-text.
4/ is true if the current absolute path has at least 4 elements. For example /home/abc/c/Users/test has 5 elements
So, if the current path has 4 or more elements, the output is ../%3d, where %3d will be replaced with the last 3 elements of the current path. For example ../c/Users/test.
If the current path has less than 4 elements, the output is %d, which will be replaced by the full current path.
BASH
Method 1: simple but not a perfect match
In bash (since version 4) you can achieve very similar results by setting PROMPT_DIRTRIM=3 and placing \w in PS1. This will also only display the last 3 elements of the current path, preceded by either ~/.../ or .../. Which depends on whether the current directory is within the user's home directory.
For example:
PS1='[\u#\h \w]\$ '
PROMPT_DIRTRIM=3
would get you
[abc#machine ~/.../c/Users/test]$
when the current working directory is /home/abc/c/Users/test and
[abc#machine .../share/doc/sometool]$
when the current working directory is /usr/local/share/doc/sometool.
Method 2: complicated but works as asked
For an exact match place the following in your PS1:
$(a=${PWD%/*} a=${a%/*} a=${a%/*}; echo ${PWD/#$a/${a:+..}})
For example
PS1='[\u#\h $(a=${PWD%/*} a=${a%/*} a=${a%/*}; echo ${PWD/#$a/${a:+..}})]\$ '
Important: At least the part that generates the path output needs to be fully quoted, e.g. surrounded by single quotes. Otherwise it would be evaluated at the time of definition and not when the prompt is displayed.
Explanation:
$(command): This is called Command Substitution. It will run command and then be substituted by the resulting output.
The parameter PWD contains the current working directory.
a=${PWD%/*}: The shortest possible match to /* will be removed from the end of $PWD and the resulting value will be assigned to parameter a. That is, the last path element will be removed from $PWD.
a=${PWD%/*} a=${a%/*} a=${a%/*}: this removes the last three path elements from $PWD. If $PWD has three or less elements, then a will be empty at the end. If there are more than three elements, then a contains all element you do not want to be shown, i.e. the ones you want to replace with ...
(Note: While a=${PWD%/*/*/*} also removes the last three path elements, it does not work as intended, if there are less than three elements. In that case the end of $PWD would not match to /*/*/* and nothing would be removed, leaving $a to be identical to $PWD.)
${a:+..}: if a is defined and not null this will be substituted by .., otherwise nothing is substituted. This means if there are path elements to be removed, then ${a:+..} will evaluate to ...
${PWD/#$a/${a:+..}}): if the beginning of $PWD matches $a then it will be replaced by the subtstitution of ${a:+..}. Essentially, if a contains any path elements, than they will be replaced by .., otherwise nothing will be changed.
echo: As this all happens within a Command Substitution, echo is needed in order to output the shortened path.

this seems to do the trick:
PS1='$(pwd|sed -r "sx.+(/[^/]*/[^/]*/[^/]*)\$x..\1x" ):'
eg:
jasen#gonzo:/var/spool/news/comp/lang/c/moderated$ PS1='$(pwd|sed -r "sx.+(/[^/]*/[^/]*/[^/]*)\$x..\1x" ):'
../lang/c/moderated:cd
/home/jasen:
How it works:
PS1 is expanded to produce the prompt.
I use command substitusion $( ... ) to insert output of a command into the prompt.
the command itself is a pipeline
first pwd pints out the current directory
it's output is piped (|) to sed in extended (-r) regular expression mode. sed is given the command.
"sx.+(/[^/]*/[^/]*/[^/]*)\$x..\1x"
this an s substitution command
in this command the symbol that follows the s is the separator here I used x
the phrase [^/]* indicates a seaquence of zero or more non-slashes (like the name of a directory) while the other slashes / represent actual slashes and .+ matches anything at all (but not nothing). and the $ represents end of line.
so starting from dollar it matches lines that end like /name/name/name
the bit after the second X where it says ..\1 is what to replace the match with. in this case .. followed by the bit contained in the bit matched by the parenthesised pattern. ../name/name/name

Related

Increase file name number by shell or linux command

I have files, for example
- public_00000.jpg
- public_00001.jpg
- ...
- public_00535.jpg
But I want to make these files as
- public_05674.jpg
- public_05675.jpg
- ...
- public_06209.jpg
I mean, I want to increase the number in the filename by +5674 on the whole.
How can I do this by Shell or Command??
Thanks ahead:)
Could you please try following.
for file in *.jpg
do
first_filename_part="${file%_*}"
last_filename_part="${file#*.}"
var="${file#*_}"
count="${var%.*}"
((count = count + 5674))
printf "%s %s %s_%05d.%s\n" "mv" $file $first_filename_part $count $last_filename_part
done
Above will only print the commands on screen like:
mv public_00000.jpg public_05674.jpg
Try running only 1 command First from above printed output on your terminal, once you are Happy with results try following then, since this will rename all the files.
for file in *.jpg
do
first_filename_part="${file%_*}"
last_filename_part="${file#*.}"
var="${file#*_}"
count="${var%.*}"
((count = count + 5674))
printf "%s %s %s_%05d.%s\n" "mv" $file $first_filename_part $count $last_filename_part | bash
done
From man page: I have used parameter expansion mechanism.
${parameter#word}
${parameter##word} Remove matching prefix pattern. The word is
expanded to produce a pattern just as in pathname expansion. If the
pattern matches the beginning of the value of parameter, then the
result of the expansion is the expanded value of parameter with the
shortest matching pattern (the #'' case) or the longest matching
pattern (the##'' case) deleted. If parameter is # or *, the
pattern removal operation is applied to each positional parame- ter in
turn, and the expansion is the resultant list. If parameter is an
array variable subscripted with # or *, the pattern removal
operation is applied to each member of the array in turn, and the
expansion is the resultant list.
${parameter%word}
${parameter%%word} Remove matching suffix pattern. The word is
expanded to produce a pattern just as in pathname expansion. If the
pattern matches a trailing portion of the expanded value of parameter,
then the result of the expansion is the expanded value of parameter
with the shortest matching pattern (the %'' case) or the longest
matching pattern (the%%'' case) deleted. If parameter is # or *,
the pattern removal operation is applied to each positional parameter
in turn, and the expansion is the resultant list. If parameter is an
array variable subscripted with # or *, the pattern removal operation
is applied to each member of the array in turn, and the expansion is
the resultant list.
You can use Perl rename like this to do an "evaluated substitution" - that's the e right at the end:
rename --dry-run 's|(\d+)|sprintf("%05d",$1+5674)|e' pub*jpg
Sample Output
'public_00000.jpg' would be renamed to 'public_05674.jpg'
'public_00001.jpg' would be renamed to 'public_05675.jpg'
In case you are unfamiliar with Perl, the command basically says:
rename "substitute|THIS|with THAT|" IN_THESE_FILENAMES
In your case, THIS is \d+ which means "one or more digits" and that is enclosed within parentheses to make a "capture group". That group can then be referred to in the substitution on the right side by $1 since it is the first capture group.
The THAT in your case is simply a print statement that prints the first capture group $1 incremented by 5674 in a field that is zero-padded to be 5 digits wide using %05d.
Using Perl rename has the benefits that:
you can do a "dry run" to see what it would do without actually doing anything
it will not clobber (overwrite) files without warning
it is fast - it doesn't create a process for sed and another for mv for every single file, it just starts a single Perl interpreter and makes a library call to rename each file
it will automagically create any intermediate directories needed, if you wish
you can use the full power of Perl to do as much substitution or calculation as you wish
Note for macOS users... Perl is installed on macOS by default, so if you use homebrew to install your packages, you just need:
brew install rename
Note for Linux users... there are several rename packages, the one I am referring to is sometimes called prename, or "Perl rename". That means, if you run file on the rename command, it should say it's a Perl script like this:
file $(which rename)
/usr/local/bin/rename: Perl script text executable

Removing an optional / (directory separator) in Bash

I have a Bash script that takes in a directory as a parameter, and after some processing will do some output based on the files in that directory.
The command would be like the following, where dir is a directory with the following structure inside
dir/foo
dir/bob
dir/haha
dir/bar
dir/sub-dir
dir/sub-dir/joe
> myscript ~/files/stuff/dir
After some processing, I'd like the output to be something like this
foo
bar
sub-dir/joe
The code I have to remove the path passed in is the following:
shopt -s extglob
for file in $files ; do
filename=${file#${1}?(/)}
This gets me to the following, but for some reason the optional / is not being taken care of. Thus, my output looks like this:
/foo
/bar
/sub-dir/joe
The reason I'm making it optional is because if the user runs the command
> myscript ~/files/stuff/dir/
I want it to still work. And, as it stands, if I run that command with the trailing slash, it outputs as desired.
So, why does my ?(/) not work? Based on everything I've read, that should be the right syntax, and I've tried a few other variations as well, all to no avail.
Thanks.
that other guy's helpful answer solves your immediate problem, but there are two things worth nothing:
enumerating filenames with an unquoted string variable (for file in $files) is ill-advised, as sjsam's helpful answer points out: it will break with filenames with embedded spaces and filenames that look like globs; as stated, storing filenames in an array is the robust choice.
there is no strict need to change global shell option shopt -s extglob: parameter expansions can be nested, so the following would work without changing shell options:
# Sample values:
file='dir/sub-dir/joe'
set -- 'dir/' # set $1; value 'dir' would have the same effect.
filename=${file#${1%/}} # -> '/sub-dir/joe'
The inner parameter expansion, ${1%/}, removes a trailing (%) / from $1, if any.
I suggested you change files to an array which is a possible workaround for non-standard filenames that may contain spaces.
files=("dir/A/B" "dir/B" "dir/C")
for filename in "${files[#]}"
do
echo ${filename##dir/} #replace dir/ with your param.
done
Output
A/B
B
C
Here's the documentation from man bash under "Parameter Expansion":
${parameter#word}
${parameter##word}
Remove matching prefix pattern. The word is
expanded to produce a pattern just as in pathname
expansion. If the pattern matches the beginning of
the value of parameter, then the result of the
expansion is the expanded value of parameter with
the shortest matching pattern (the ``#'' case) or
the longest matching pattern (the ``##'' case)
deleted.
Since # tries to delete the shortest match, it will never include any trailing optional parts.
You can just use ## instead:
filename=${file##${1}?(/)}
Depending on what your script does and how it works, you can also just rewrite it to cd to the directory to always work with paths relative to .

how to filter a command subsitution from the resulting value of a readlink for symlink?

This may be poorly titled as I'm not fully sure what the process is called.
Basically I want to get only the last part of a symlink path, and I'm trying to use the same method I use with PWD.
For example:
if I do
PWD
it prints
/opt/ct/mydir
if I do
echo ${PWD##*/}
it prints only the last part
mydir
So using that design I can do
readlink mysymlink
which gives
/opt/ct/somedir
and I can do
TMP=$(readlink mysymlink)
echo ${TMP##*/}
and it will print
somedir
So now how can I combine that last part into one line like
TMP=$(readlink mysymlink && echo ${TMP##*/})
???
The example I show gives me 2 concatenated results.. one with the full path and one with just the part I want. I only want that last directory.
I also tried
TMP=${ $(readlink mysymlink)##*/}
to no avail
Variable substitution suffixes can only be used with variables, not command substitutions. You either have to set the variable and modify it in separate statements, as in your first attempt, or use additional command substitutions:
TMP=$(basename $(readlink))

Linux shell list file what's the difference bewteen tmp/**/* and tmp/*

I encounter one problem about the file system in the shell.
what's difference between tmp/**/* and tmp/*?
I make the experiment in my system,
have this directory dir2
dir2
-->dir1
-->xx2
-->ff.txt
and I run ls dir2/*:
dir2/ff.txt
dir2/dir1:
xx2
then I run ls dir2/**/*:
dir2/dir1/xx2
So it means the ** is to ignore this directory(like ignore the dir1),
Can some one help me ?
I think there's a formatting issue in the question test, but I'll answer based on the question title and examples.
There shouldn't be any difference between a single and double asterisk at any single level of the path. Either expression matches any name, except for hidden ones which start with a dot (this can be changed by shell options). So:
tmp/**/* (equivalent to tmp/*/*) is expanded to all names which are nested two levels deep in tmp. The first asterisk expands only to directories and not files at the first level because it's followed by a slash.
tmp/* expands to anything nested one level deep inside tmp.
To this comes the fact that ls will list contents of directory if a directory is given on its command line. This can be overridden by adding -d option to ls.

Bash path issue

I have a script which contains the following line:
propFile="${0%/*}/anteater.properties"
What does "${0%/*}" mean?
This command gives a path to the script - but there is a spaces at path and script can't find this file - how to deal with it?
The % operator in variable expansion removes the matching suffix pattern given to it. So ${0%/*} takes the variable $0, and removes all matching /* at the end. This is equivalent to the command dirname, which, when given a path, returns the parent directory of that path.
In order to deal with spaces in bash variable, whenever expanding the variable (i.e. whenever you write $var), you should quote it. In short, always use "$var" instead of just $var.
Consider reading shell parameter expansion and variable quoting in the bash manual to learn more about these two subjects.
strips the suffix matching /*, i.e. everything after last slash including the slash itself.
quote it wherever you use it (cat "$propFile").

Resources