3

For example I have files :

file0.txt
file1.txt
file2.txt
....
file100.txt

I would like to copy 5 files in bash, which will produce result :

file0.txt
file19.txt
file39.txt
file59.txt
file79.txt

Edit. screwed up counting should be this:

file0.txt
file20.txt
file40.txt
file60.txt
file80.txt

So files are evenly spaced sampled. Hope there is some cool oneliner.

0

4 Answers 4

10

Note that brace expansion is not globbing, it will expand regardless of whether words in the result refer to actual files. If you want to copy only those of those files that actually exist, you could do:

shopt -s failglob extglob
cp file@(0|[1357]9).txt  /path/to/destination/

In zsh, you can force globbing interpretation on arbitrary strings by appending a (N) (for nullglob) qualifier.

cp file{0,{1..79..20}}.txt(N) /path/to/destination/

We need nullglob to avoid the error if any of the glob expansions fail to match anything, but that means that if none of them match, cp /path/to/destination/ will be run. So strictly speaking, it should be:

(){ if (($#)) cp $@ /path/to/destination; } file{0,{19..79..20}}.txt(N)

Or alternatively construct a (0|19|...) glob dynamically with:

() { cp file(${(~j[|])@}).txt /path/to/destination; } 0 {19..79..20}

This time without nullglob, so you'll get the correct no match error if not file was found.

To copy one every 20 up to 5 of the numerically sorted list of file<digits>.txt:

() {
  printf -v argv '%s%20$.0s' $argv
  cp -- $argv[1,5] /path/to/destination
} file<->.txt(n)
4
  • 1
    Stéphane, it might be helpful to explain what the () {command;} structure is. It looks like it is creating an anonymous function, but that can't be it since the command is actually being executed. So it must be some clever zsh trickery.
    – terdon
    Nov 29, 2023 at 10:14
  • 1
    @terdon it is an anonymous function and the docs say "The function is executed immediately at the point of definition"
    – muru
    Nov 29, 2023 at 10:54
  • @terdon, well anonymous functions are executed. What would be the point of them otherwise? You'll find literally hundreds of answers by me here where those constructs are described. I'll add some description here at some point if I have the time. Nov 29, 2023 at 10:54
  • That seems obvious in retrospect, but I am just not familiar with anonymous functions, they're not something I am used to. Since it looks like a function definition, I was surprised to see it executed, but you're quite right: since there is no name, the only way this could be useful is if they were directly executed. Bash doesn't support that though, so I was confused.
    – terdon
    Nov 29, 2023 at 11:09
7

Brace expansion in bash supports steps in the format {<start>..<end>..<step>}:

$ echo file{0..100..19}.txt
file0.txt file19.txt file38.txt file57.txt file76.txt file95.txt

Though it seems you need an irregular spacing:

$ echo file0.txt file{19..100..20}.txt
file0.txt file19.txt file39.txt file59.txt file79.txt file99.txt
0
2

There isn't a useful pattern in the set of file names (numbers) you've provided, so the simplest "one liner" you can hope for is:

cp file0.txt file19.txt file39.txt file59.txt file79.txt destination_dir/

I had looked at splitting the interval 0..100 by five, or adding 20 to each increment, but neither gives the set of files you've specified.

If you aren't particular about which set of files you want, then it's possible to take the full set of 101 files, divide it by the number of files required, and then increment along the collection picking out target files. Here's an example of that in bash:

files=(*)                                                 # Assume current directory
samples=5                                                 # How many required

total=${#files[@]}; echo total=$total                     # Number of files
interval=$(( total/samples )); echo interval=$interval    # Integer arithmetic

for ((i=0; i<samples; i++))
do
    fileNo=$((i*interval))                                # Which sample
    echo i=$i, fileNo=$fileNo, file=${files[$fileNo]}     # Chosen sample
    ## cp -- "${files[$fileNo]}" destination_dir/         # Copy the file
done

Output

total=101
interval=20
i=0, fileNo=0, file=file0.txt
i=1, fileNo=20, file=file26.txt
i=2, fileNo=40, file=file44.txt
i=3, fileNo=60, file=file62.txt
i=4, fileNo=80, file=file80.txt

As you can see the selected files do not match your required set, being file0.txt, file26.txt, file44.txt, file64,txt, and file80.txt.

1

If your system has jot installed, it can do this:

$ jot -w 'cp "file%d.txt" "destination/"' 5 0 100

That command tells jot to generate 5 numbers between 0 and 100, inclusive. Each number is substituted in the %d part of the -w print specification, so for example, if you need leading zeroes you'd use ... -w 'cp "file%03d.txt" ...

Inspect those commands, and if they look like what you want to do, then up-arrow and pipe it to sh.

$ jot -w 'cp "file%d.txt" "destination/"' 5 0 100
cp "file0.txt" "destination/"
cp "file25.txt" "destination/"
cp "file50.txt" "destination/"
cp "file75.txt" "destination/"
cp "file100.txt" "destination/"
$ jot -w 'cp "file%d.txt" "destination/"' 5 0 100 | sh

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .