Mục đích của việc sử dụng shift trong shell script là gì?


118

Tôi đã xem qua kịch bản này:

#! /bin/bash                                                                                                                                                                                           

if (( $# < 3 )); then
  echo "$0 old_string new_string file [file...]"
  exit 0
else
  ostr="$1"; shift
  nstr="$1"; shift  
fi

echo "Replacing \"$ostr\" with \"$nstr\""
for file in $@; do
  if [ -f $file ]; then
    echo "Working with: $file"
    eval "sed 's/"$ostr"/"$nstr"/g' $file" > $file.tmp 
    mv $file.tmp $file
  fi  
done

Ý nghĩa của các dòng họ sử dụng là shiftgì? Tôi đoán kịch bản nên được sử dụng với ít nhất các đối số để ...?

Câu trả lời:


141

shiftlà một loại tích bashhợp trong đó loại bỏ các đối số từ đầu danh sách đối số. Cho rằng 3 đối số cung cấp cho các kịch bản có sẵn trong $1, $2, $3, sau đó một cuộc gọi đến shift sẽ làm $2mới $1. A shift 2sẽ thay đổi bởi hai làm mới $1cái cũ $3. Để biết thêm thông tin, xem tại đây:


25
+1 Lưu ý rằng nó không xoay chúng, nó sẽ dịch chuyển chúng ra khỏi mảng (của các đối số). Shift / unshift và pop / push là những tên phổ biến cho loại thao tác chung này (xem, ví dụ, bash's pushdpopd).
goldilocks

1
Các tài liệu tham khảo dứt khoát: gnu.org/software/bash/manual/bashref.html#index-shift
glenn jackman

@goldilocks rất đúng. Thay vì "xoay" tôi nên tìm ra cách sử dụng một từ khác, tốt hơn là "di chuyển các đối số về phía trước trong các biến đối số". Dù sao, tôi hy vọng các ví dụ trong văn bản và liên kết đến tài liệu tham khảo sẽ làm cho nó rõ ràng dù sao.
nhân

Mặc dù kịch bản đề cập bash, câu hỏi chung cho tất cả các hệ vỏ, do đó, tiêu chuẩn Posix chiếm ưu thế: pubs.opengroup.org/onlinepub/9699919799/utilities/ trộm
calandoa

32

Như nhận xét của Goldilocks và các tài liệu tham khảo của nhân loại mô tả, shiftgán lại các tham số vị trí ( $1, $2v.v.) để $1lấy giá trị cũ của $2, $2lấy giá trị của $3, v.v. *   Giá trị cũ của $1bị loại bỏ. ( $0không thay đổi.) Một số lý do để thực hiện việc này bao gồm:

  • Nó cho phép bạn truy cập đối số thứ mười (nếu có) một cách dễ dàng hơn.  $10không hoạt động - nó được hiểu là $1nối với một 0 (và do đó có thể tạo ra một cái gì đó như Hello0). Sau một shift, đối số thứ mười trở thành $9. (Tuy nhiên, trong hầu hết các shell hiện đại, bạn có thể sử dụng ${10}.)
  • Như Hướng dẫn Bash cho người mới bắt đầu chứng minh, nó có thể được sử dụng để lặp qua các đối số. IMNSHO, điều này thật vụng về; fortốt hơn nhiều cho điều đó.
  • Như trong kịch bản ví dụ của bạn, nó giúp dễ dàng xử lý tất cả các đối số theo cùng một cách trừ một số ít. Ví dụ: trong tập lệnh của bạn $1$2là các chuỗi văn bản, trong khi $3và tất cả các tham số khác là tên tệp.

Vì vậy, đây là cách nó diễn ra. Giả sử tập lệnh của bạn được gọi Patryk_scriptvà nó được gọi là

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

Kịch bản thấy

$1 = USSR
$2 = Russia
$3 = Treaty1
$4 = Atlas2
$5 = Pravda3

Câu lệnh ostr="$1"đặt biến ostrthành USSR. Câu shiftlệnh đầu tiên thay đổi các tham số vị trí như sau:

$1 = Russia
$2 = Treaty1
$3 = Atlas2
$4 = Pravda3

Câu lệnh nstr="$1"đặt biến nstrthành Russia. Câu shiftlệnh thứ hai thay đổi các tham số vị trí như sau:

$1 = Treaty1
$2 = Atlas2
$3 = Pravda3

Và sau đó forthay đổi vòng lặp USSR( $ostr) để Russia( $nstr) trong các tập tin Treaty1, Atlas2Pravda3.



Có một vài vấn đề với kịch bản.

  1. cho tệp trong $ @; làm

    Nếu tập lệnh được gọi là

    Patryk_script Hiệp ước Liên Xô Nga1 "World Atlas2" Pravda3

    nó thấy

    $ 1 = Liên Xô
    $ 2 = Nga
    $ 3 = Hiệp ước1
    $ 4 = Thế giới Atlas2
    $ 5 = Pravda3

    nhưng, vì $@không được trích dẫn, không gian trong World Atlas2không trích dẫn, và các forvòng lặp nghĩ nó có bốn tập tin: Treaty1, World, Atlas2, và Pravda3. Điều này nên là một trong hai

    cho tệp trong "$ @"; làm

    (để trích dẫn bất kỳ ký tự đặc biệt nào trong các đối số) hoặc đơn giản là

    cho tập tin làm

    (tương đương với phiên bản dài hơn).

  2. eval "sed 's /" $ lasr "/" $ nstr "/ g' $ file"

    Không cần điều này là một eval, và chuyển đầu vào của người dùng không được kiểm soát sang một evalcó thể nguy hiểm. Ví dụ: nếu tập lệnh được gọi là

    Patryk_script "'; rm *;'" Hiệp ước Nga1 Atlas2 Pravda3

    nó sẽ thực thi rm *! Đây là một mối quan tâm lớn nếu tập lệnh có thể được chạy với các đặc quyền cao hơn so với những người dùng gọi nó; ví dụ: nếu nó có thể được chạy qua sudohoặc được gọi từ giao diện web. Nó có thể không quá quan trọng nếu bạn chỉ sử dụng nó như chính mình, trong thư mục của bạn. Nhưng nó có thể được thay đổi thành

    sed "s / $ lasr / $ nstr / g" "$ file"

    Điều này vẫn có một số rủi ro, nhưng chúng ít nghiêm trọng hơn nhiều.

  3. if [ -f $file ], > $file.tmpmv $file.tmp $file nên if [ -f "$file" ], > "$file.tmp"mv "$file.tmp" "$file", tương ứng, để xử lý tên tập tin mà có thể có các khoảng trống (hoặc nhân vật hài hước khác) trong đó. ( eval "sed …Lệnh cũng mang tên tệp có không gian trong đó.)


* shift lấy một đối số tùy chọn: một số nguyên dương xác định số lượng tham số cần dịch chuyển. Mặc định là một ( 1). Ví dụ, shift 4nguyên nhân $5 để trở thành $1, $6trở thành $2, và như vậy. (Lưu ý rằng ví dụ trong Hướng dẫn Bash cho người mới bắt đầu là sai.) Và do đó, tập lệnh của bạn có thể được sửa đổi để nói

ostr="$1"
nstr="$2"
shift 2

mà có thể được coi là rõ ràng hơn.



Lưu ý cuối / Cảnh báo:

Ngôn ngữ Windows Command Prompt (tệp bó) cũng hỗ trợ một SHIFTlệnh, về cơ bản giống như shiftlệnh trong shell Unix, với một điểm khác biệt nổi bật, tôi sẽ ẩn để cố gắng ngăn mọi người khỏi bị nhầm lẫn bởi nó:

  • Một lệnh giống như SHIFT 4là một lỗi, mang lại một tham số không hợp lệ cho lệnh SHift thông báo lỗi lỗi.
  • SHIFT /n, trong đó nmột số nguyên nằm trong khoảng từ 0 đến 8, là hợp lệ - nhưng nó không thay đổi thời gian . Nó thay đổi một lần, bắt đầu với các n  tranh luận thứ. Vì vậy, làm cho (đối số thứ năm) trở thành trở thành , và cứ thế, để lại các đối số 0 đến 3.n SHIFT /4%5%4,  %6%5


2
shiftcó thể được áp dụng cho while, untilvà thậm chí forcác vòng lặp để xử lý các mảng theo những cách tinh tế hơn nhiều so với một forvòng lặp đơn giản . Tôi thường thấy nó hữu ích trong forcác vòng lặp như ... set -f -- $args; IFS=$split; for arg do set -- $arg; shift "${number_of_fields_to_discard}" && fn_that_wants_split_arg "$arg"; done
mikeerv

3

Giải thích đơn giản nhất là đây. Hãy xem xét lệnh:

/bin/command.sh SET location Cebu
/bin/command.sh SET location Cebu, Philippines 6014 

Nếu không có sự giúp đỡ của shiftbạn, bạn không thể trích xuất toàn bộ giá trị của vị trí vì giá trị này có thể kéo dài tùy ý. Khi bạn thay đổi hai lần, các đối số SETlocationđược loại bỏ sao cho:

x="$@"
echo "location = $x"

Hãy nhìn thật lâu vào $@điều đó. Điều đó có nghĩa là, nó có thể lưu vị trí hoàn chỉnh thành biến xbất chấp khoảng trắng và dấu phẩy mà nó có. Vì vậy, tóm lại, chúng tôi gọi shiftvà sau đó chúng tôi lấy giá trị của những gì còn lại từ biến $@.

CẬP NHẬT Tôi đang thêm vào bên dưới một đoạn rất ngắn cho thấy khái niệm và tính hữu dụng của việc shiftkhông có nó, sẽ rất, rất khó để trích xuất các trường chính xác.

#!/bin/sh
#

[ $# -eq 0 ] && return 0

a=$1
shift
b=$@
echo $a
[ -n "$b" ] && echo $b

Giải thích : Sau khi shift, biến b sẽ chứa phần còn lại của nội dung được truyền vào, bất kể khoảng trắng hay v.v ... Đây [ -n "$b" ] && echo $blà một bảo vệ sao cho chúng ta chỉ in b nếu nó có nội dung.


(1) Đây không phải là lời giải thích đơn giản nhất. Đó là một góc thú vị. (2) Nhưng miễn là bạn muốn kết hợp một loạt các đối số (hoặc tất cả chúng), bạn có thể muốn tập thói quen sử dụng "$*"thay vì "$@". (3) Tôi sẽ nâng cao câu trả lời của bạn, nhưng tôi đã không làm thế, bởi vì bạn nói rằng Dịch chuyển hai lần hai lần nhưng bạn không thực sự hiển thị nó trong mã. (4) Nếu bạn muốn tập lệnh có thể lấy giá trị nhiều từ từ dòng lệnh, có lẽ tốt hơn là yêu cầu người dùng đặt toàn bộ giá trị vào dấu ngoặc kép. Tiết (Cont'd)
Scott

(Tiếp theo) Bạn có thể không quan tâm hôm nay, nhưng cách tiếp cận của bạn không cho phép bạn có nhiều khoảng trắng ( Philippines   6014), khoảng trắng hàng đầu, dấu cách hoặc dấu tab. (5) BTW, bạn cũng có thể thực hiện những gì bạn đang làm ở đây trong bash với x="${*:3}".
Scott

Tôi đã cố gắng giải mã nơi bình luận '*** của bạn không cho phép nhiều khoảng trống ***' đến từ vì điều này không liên quan đến shiftlệnh. Bạn có thể đề cập đến sự khác biệt tinh tế $ @ so với $ *. Nhưng sự thật vẫn còn đó, rằng đoạn trích của tôi ở trên có thể trích xuất chính xác vị trí cho dù nó có bao nhiêu dấu vết hoặc ở phía trước nó không thành vấn đề. Sau ca làm việc, vị trí sẽ chứa vị trí chính xác.
typelogic

2

shift coi các đối số dòng lệnh như là một hàng đợi FIFO, nó là phần tử popleft mỗi khi nó được gọi.

array = [a, b, c]
shift equivalent to
array.popleft
[b, c]
$1, $2,$3 can be interpreted as index of the array.
$# is the length of array
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.