Complete files in a different directory in zsh - autocomplete

Does the compadd command for ZSH not support completion when some characters are entered?
I have a executable file called 'index_for_test.js',and i add a shell script to .zshrc.
$PATH:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/hanqing/Development/compadd-test
the index_for_test.js at the root of /Users/hanqing/Development/compadd-test
./
├── dir1
│ ├── a.js
│ └── b.js
├── dir2
│ ├── a.ts
│ └── b.ts
└── index_for_test.js
total 24
drwxr-xr-x 6 hanqing staff 192 11 4 13:45 .
drwxr-xr-x 10 hanqing staff 320 11 4 13:42 ..
-rw-r--r--# 1 hanqing staff 6148 11 4 14:04 .DS_Store
drwxr-xr-x 4 hanqing staff 128 11 4 13:43 dir1
drwxr-xr-x 4 hanqing staff 128 11 4 13:43 dir2
-rwxr-xr-x 1 hanqing staff 155 11 4 13:50 index_for_test.js
// index_for_test.js
#! /usr/bin/env node
const fs =require('fs')
const path=require('path')
const files=fs.readdirSync(path.join(process.cwd()))
console.log(files.join('\n'))
the script be added to .zshrc :
_index_for_test_completion() {
local abc=(`index_for_test.js`)
echo '\nabc:\n'
echo $abc'\n'
compadd -- $abc
}
compdef _index_for_test_completion index_for_test.js
When input index_for_test.js followed by input a space then press tab, it work find.But when input index_for_test.js ../ then press tab,it does not show completion list, even if compadd accept the arguments.
Image:
normal: index_for_test.js
error: index_for_test.js ../
Expect
If this is my mistake,please let me know the reason,thanks.
In addition,if the behavior is right,I wonder that how to achieve completion like cd command;
behavior of cd

index_for_test.js only prints names of files in the current directory. A file name in the current directory cannot start with ../, so there is no completion starting with ../.
If you want to complete files in a directory, you need to pass this directory to your completion script, and have it work in that directory.
In addition, you should complete directory names inside zsh, if all directories may potentially contain interesting files. If you want to allow only certain directories, have your script complete directories.
In addition, your script is broken when file names contain whitespace. Use a null byte as the separator: file names can't contain null bytes.
Untested code. May need some tweaking.
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const files = fs.readdirSync(process.argv[2]);
console.log(files.join('\0'))
_index_for_test_completion() {
local dir=$words[CURRENT] files
if [[ ! -d $dir ]]; then dir=$dir:h; fi
files=(${(ps:\0:)$(index_for_test.js $dir))
print -lr '' abc: $files
if [[ $dir != $words[CURRENT] ]]; then dir+="/"; fi
compadd -- $^dir$files
}
compdef _index_for_test_completion index_for_test.js

Related

How do I create multiple directories within a directory that's in a directory with a single line command

so my path is from the desktop and I tried: mkdir OS\LAB1\Finance,Public,Archive,Customer
The command makes the first 3 directories but the last three get made on my desktop and not within LAB1,how do I do it with one command line?
Thank you
not a one liner, but this does the trick
$folders = #(,'Public','Archive','Customer')
New-Item -ItemType Directory C:\OS\LAB1\Finance\ -Force
foreach($folder in $folders){
New-Item -ItemType Directory C:\OS\LAB1\Finance\$folder -Force
}
The force parameter creates all the directories in one go.
I think that you want to create 4 directories under LAB1 not a single directory four deep. in Bash you can do this.
for i in $(tr ',' '\n' <<< "Finance,Public,Archive,Customer"); do mkdir "$i"; done
Result looks like this
0 drwxr-xr-x# 7 camerontownshend staff 224 16 Mar 12:28 .
0 drwxr-xr-x# 147 camerontownshend staff 4704 16 Mar 11:13 ..
0 drwxr-xr-x# 2 camerontownshend staff 64 16 Mar 12:28 Archive
0 drwxr-xr-x# 2 camerontownshend staff 64 16 Mar 12:28 Customer
0 drwxr-xr-x 2 camerontownshend staff 64 16 Mar 12:28 Finance
0 drwxr-xr-x# 2 camerontownshend staff 64 16 Mar 12:28 Public
Explanation
for i in ... -> for loop
tr ',' '\n' -> replaces commas with new lines
do mkdir "$i" done -> run whatever is inside the do/done pair. The mkdir "$i" - substitutes the iterator from the loop as a variable into the mkdir command
To create multiple folders (full paths) in one line using a list (caution: no spaces after commas):
$ mkdir -p /tmp/{test1,test2}
To verify:
$ ls /tmp/test*
/tmp/test1:
/tmp/test2:

Move folders that have more than one file into another directory

POWER SHELL ERROR Picture of code and directories
I would like to create a batch file that moves all folders that contain more than one file to another directory.
I tried the code below
mkdir "OOOO3_MORE_THAN_ONE"
for dir in *; do
# if the file is a directory
if [ -d "$dir" ]; then
# count number of files
count=$(find "$dir" -type f | wc -l)
#i f count=2 then move
if [ "$count" -le 1 ]; then
# move dir
mv -v "$dir" /completepath/"OOOO3_MORE_THAN_ONE"
fi
fi
done
I just get a new folder without any folders inside. The folders with multiple files did not move to the new directory
I also tried the below code, it's a little different, but still resulted in an empty folder
#! /bin/bash -p
shopt -s nullglob # glob patterns that match nothing expand to nothing
shopt -s dotglob # glob patterns expand names that start with '.'
destdir='subset'
[[ -d $destdir ]] || mkdir -- "$destdir"
for dir in * ; do
[[ -L $dir ]] && continue # Skip symbolic links
[[ -d $dir ]] || continue # Skip non-directories
[[ $dir -ef $destdir ]] && continue # Skip the destination dir.
numfiles=$(find "./$dir//." -type f -print | grep -c //)
(( numfiles > 1 )) && mv -v -- "$dir" "$destdir"
done
Alright, you have two problems (as originally posted).
(1) you are attempting to move to /completepath/OOOO3_MORE_THAN_ONE after creating "OOOO3_MORE_THAN_ONE" in the current working directory. Unless you are executing the script in /completepath when the directory ./OOOO3_MORE_THAN_ONE is created, your calls to mv -v "$dir" /completepath/"OOOO3_MORE_THAN_ONE"will fail. (the double-quotes are superfluous here)
(2) you have a "chicken-and-the-egg" problem because you:
mkdir "OOOO3_MORE_THAN_ONE"
before you call:
for dir in *; do
(OOOO3_MORE_THAN_ONE is included in '*', but not excluded before your calls to find and mv)
So you will effectively try and move OOOO3_MORE_THAN_ONE below itself when its name is reached in your list.
So How To Correct the Problems?
Rearrange your code. Since your Question is tagged [sh] (POSIX shell), you do not have the benefit of arrays available to pre-store the count and dir names, but you can always use a temporary file created with mktemp. You will want to read through each directory entry identified with for dir in * and write all count and dir information out to your temporary file before you start changing the directory structure. Then you can simply loop over the entries in your temporary file, checking if $count -gt 1 and moving to your new $dir if so, e.g.
#!/bin/sh
## initialize newdir from 1st argument (or default: OOOO3_MORE_THAN_ONE)
newdir="${1:-OOOO3_MORE_THAN_ONE}"
## set your complete path from 2nd arg (or '.' by default)
cmpltpath="${2:-.}"
## now create a temporary file to hold count dirname pairs
tmpfile=$(mktemp)
for dir in *; do ## write count and dirname pairs to temporary file
[ -d "$dir" ] && echo "$(find "$dir" -type f | wc -l) $dir" >> "$tmpfile"
done
## now create the directory to move to using cmpltpath, exit on failure
mkdir -p "$cmpltpath/$newdir" || exit 1
## read count and dirname from tmpfile & move if count > 1
while read -r count dir || [ -n "$dir" ]; do
## if count=2 then move
if [ "$count" -gt 1 ]; then
## move to dir
mv -v "$dir" "$cmpltpath/$newdir"
fi
done < "$tmpfile"
rm "$tmpfile" ## tidy up and remove tmpfile (or set trap after mktemp)
(note: the script takes the directory name to create and move files below as the first argument (positional parameter) for the script, and the complete path (absolute or relative) to precede your new directory)
(also note: if you have bash (or another advanced shell that supports associative arrays), you can simply save the directory name and count within an associative array keyed on directory name and avoid using a temporary file altogether)
Original directory
Using a directory tree where each subdirectory d1, d2, d3 has 1, 2 or 3 files below them, e.g.:
$ tree
.
├── d1
│   └── file1
├── d2
│   ├── file1
│   └── file2
├── d3
│   ├── file1
│   ├── file2
│   └── file3
└── mvscript.sh
Example Use/Resulting Directory Structure
Now running the script will move all directories with greater than 1 file below into your new directory:
$ sh mvscript.sh
'd2' -> './OOOO3_MORE_THAN_ONE/d2'
'd3' -> './OOOO3_MORE_THAN_ONE/d3'
$ tree
.
├── OOOO3_MORE_THAN_ONE
│   ├── d2
│   │   ├── file1
│   │   └── file2
│   └── d3
│   ├── file1
│   ├── file2
│   └── file3
├── d1
│   └── file1
└── mvscript.sh
Your Second Approach
Your second approach is not bad, but unless you have some special requirements and need to match dot-files, you may want to adjust GLOBIGNORE instead of setting dotglob as specified in man bash under the Pathname Expansion section. Also note there is no space between the #! and /bin/bash on the first line.
A basic tweak of your second attempt could be:
#!/bin/bash
destdir='subset'
mkdir -p -- "$destdir" || exit 1
for dir in * ; do
[[ -L $dir ]] && continue # Skip symbolic links
[[ -d $dir ]] || continue # Skip non-directories
[[ $dir -ef $destdir ]] && continue # Skip the destination dir.
numfiles=$(find "$dir" -type f -printf ".\n" | wc -l)
(( numfiles > 1 )) && mv -v -- "$dir" "$destdir"
done
Example Use/Output
A similar test would result in:
$ bash mvscript2.sh
'd2' -> 'subset/d2'
'd3' -> 'subset/d3'
$ tree
.
├── d1
│   └── file1
├── mvscript2.sh
└── subset
├── d2
│   ├── file1
│   └── file2
└── d3
├── file1
├── file2
└── file3

Alias directory minus one file

I'd like to make an alias for a directory that contains many files. If I open the aliased directory, I want to view all files except for one. New files will be added to the directory over time, and I want everything to be aliased except for the one file permanently not-aliased.
Is it possible to set this up without needing to run a script every time a file in the directory changes?
E.g.: myDir contains fileA, fileB, and fileC.
I want myAliasedDir to contain fileA and fileB, but not fileC.
Can you get away with symbolic links?
Assuming your shell is bash:
$ shopt -s extglob
$ mkdir dir1 dir2
$ cd dir1
$ touch file{1..3}
$ ls -l
total 0
-rw-r--r-- 1 jackman jackman 0 Apr 1 12:40 file1
-rw-r--r-- 1 jackman jackman 0 Apr 1 12:40 file2
-rw-r--r-- 1 jackman jackman 0 Apr 1 12:40 file3
$ cd ../dir2
$ for f in ../dir1/!(file2); do ln -s "$f"; done
$ ls -l
total 8
lrwxrwxrwx 1 jackman jackman 13 Apr 1 12:41 file1 -> ../dir1/file1
lrwxrwxrwx 1 jackman jackman 13 Apr 1 12:41 file3 -> ../dir1/file3

Need help to move files from one directory to another with shell script

There will be a directory, which will contain various file types(xlsx, gpg, txt).
If .gpg then only invoke the decrypt.sh or else move the file to output folder.
Anyone who could help me the same?
Assuming bash (likely), you can iterate over the files in a directory with the for command, such as in the following transcript:
pax> ls -al
total 4
drwxr-xr-x 5 pax ubergeeks 0 Jun 7 16:01 .
drwxr-xr-x 1 pax ubergeeks 8192 Jun 7 16:01 ..
-rw-r--r-- 1 pax ubergeeks 0 Jun 7 16:01 file 1.txt
-rw-r--r-- 1 pax ubergeeks 0 Jun 7 16:01 file 2.gpg
-rw-r--r-- 1 pax ubergeeks 0 Jun 7 16:01 file 3.xlsx
pax> for fspec in *.txt *.gpg *.xlsx ; do
...> echo "==> '$fspec'"
...> done
==> 'file 1.txt'
==> 'file 2.gpg'
==> 'file 3.xlsx'
You can test if a string variable ends in a specific string by using the regular expression operator:
if [[ "${fspec}" =~ \.gpg$ ]] ; then
echo it ends with .gpg
fi
And, of course, you can run a script or move a file with commands like:
/path/to/decrypt.sh "${fspec}
mv "${fspec}" /path/to/output
So, combining all that, a good starting point would be (making sure you specify real paths rather than my /path/to/ placeholders):
#!/usr/bin/env bash
cd /path/to/files
for fspec in *.txt *.gpg *.xlsx ; do
if [[ "${fspec}" =~ \.gpg$ ]] ; then
echo "Decrypting '${fspec}'"
/path/to/decrypt.sh "${fspec}"
else
echo "Moving '${fspec}'"
mv "${fspec}" /path/to/output
fi
done

How can I non-recursively migrate directories with Perl or shell?

We're migrating home folders to a new filesystem, and I am looking for a way to automate it using Perl or a shell script. I don't have much choice in programming languages as the systems are proprietary storage clusters that should remain as unchanged as possible.
Task: Under directory /home/ I have various users' home folders aaa, bbb, ccc, ... and they have certain permissions and user/group ownership that need to remain intact upon migration to /newhome/. Here's example of what needs to be migrated from /home:
drwxr-xr-x 3 aaaaa xxxxxxxxx 4096 Feb 26 2008 aaaaa/
drwxrwxrwx 88 bbbbbbb yyyyyy 8192 Dec 16 16:32 bbbbbbb/
drwxr-xr-x 6 ccccc yyyyyy 4096 Nov 24 04:38 ccccc/
drwxr-xrwx 36 dddddd yyyyyy 4096 Jun 20 2008 dddddd/
drwxr-xr-x 27 eee yyyyyy 4096 Dec 16 02:56 eee/
So, exact same folders with permissions and ownerships should be created under /newhome. Copying/moving files should not be a concern, as it will be handled later.
Anyone has worked on such script? I am really new to Perl, so I need help.
cp's -a flag will maintain permission, modification times etc. You should for be able to do something like:
for a in `ls /home`; do cp -a "/home/$a" "/newhome/$a" ; done
Try it with one directory to see if does what you need before automating it.
EDIT: You can disable recursive file copying by using rsync or tar as mentioned by Paul. With rsync, subdirectories are still preserved, but files aren't copied:
sudo rsync -pgodt /home/ /newhome/
I haven't tried tar's --no-recursion, so can't comment on it.
EDIT 2: Another way
find /home/ -maxdepth 1 -print | sudo cpio -pamVd /newhome
Reference
You can only preserve the owner and group if you do the copying operation as root. Most of the commands given will work - the tar and the cp -rp options will.
The only trick to worry about is non-writable directories, but that's an issue for non-root users. Then, I tend to use:
(cd /home; find . -depth) | cpio -pvdumB /newhome
The -depth option means that file and sub-directories are processed before the directories themselves, so the no-write permission on the directory is only set after all the contents of the directory have been copied into it. You can also use a 'sort -r' to list files in reverse order, which ensures that directories appear after their contents.
This will create the directories and copy all the files.
cd /home; tar cvBf - . | (cd /newhome; tar xvpBf -)
If you don't want to copy all the files, you might be able to do that by adding a "--no-recursion" to the first tar command.
If these directories are on the same filesystem, why not simply
cp -p /home/* /newhome/